Skip to content

Commit 40c09d8

Browse files
gtrrz-victorclaude
andcommitted
fix(codex): set tailer emitted flag before send to close scale-flap race
The rollout token tailer stored its emitted flag after sending each Tokens event, leaving a window where a consumer observed the tailer's cumulative Tokens while emitted was still false. A concurrent turn.completed envelope then read the flag as false and leaked a per-turn-scale token value, flapping the live counter between per-turn and session-cumulative scales and making the final value nondeterministic. Store before the send so the flag is observable no later than the Tokens event itself. Fixes flaky TestParseCodexOutput_TailerSuppressesPerTurnStdoutTokens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01KYKXTGZF3PAB8PKZTYERN2D7
1 parent b0fd1e7 commit 40c09d8

1 file changed

Lines changed: 13 additions & 4 deletions

File tree

cmd/entire/cli/agent/codex/review_tokens.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ const (
3535
// token_count.total_token_usage is a running SESSION total (not per-turn
3636
// scale like turn.completed usage), so each emission is an absolute count —
3737
// matching consumers' overwrite-not-sum semantics. Duplicate totals are
38-
// suppressed so we only emit on real movement. emitted is set after the
39-
// first successful send; the parser uses it to suppress its per-turn-scale
40-
// stdout emissions so a single source stays authoritative.
38+
// suppressed so we only emit on real movement. emitted is set immediately
39+
// before each send (never after — see drain) so it is observable no later
40+
// than the Tokens event itself; the parser uses it to suppress its
41+
// per-turn-scale stdout emissions so a single source stays authoritative.
4142
//
4243
// Returns when stop is closed (the stdout stream ended) — after one final
4344
// catch-up drain of the file, so the last token_count codex wrote is not
@@ -126,11 +127,19 @@ func (t *rolloutTail) drain() error {
126127
continue
127128
}
128129
t.lastIn, t.lastOut = in, outTok
130+
// Set emitted BEFORE the send: the parser suppresses its
131+
// per-turn-scale turn.completed Tokens once the tailer has
132+
// emitted, so the flag must be observable no later than the
133+
// Tokens event itself. Storing after the send leaves a window
134+
// where a consumer sees the tailer's Tokens while emitted is
135+
// still false, letting a concurrent turn.completed leak a
136+
// per-turn value and flap the counter between scales.
137+
//
129138
// Unconditional send is safe: the parser waits for the
130139
// tailer before closing the channel, and the run contract
131140
// guarantees the consumer drains until close.
132-
t.out <- reviewtypes.Tokens{In: in, Out: outTok}
133141
t.emitted.Store(true)
142+
t.out <- reviewtypes.Tokens{In: in, Out: outTok}
134143
}
135144
}
136145
if readErr != nil {

0 commit comments

Comments
 (0)