Repository navigation
state: let work clear the idle hint, so a background job is not drawn as idle - #8
Merged
Merged
Conversation
… as idle
A pane attached to a background job session drew the idle screen, "watching
for activity", over a main agent that was starting a Bash call every few
seconds. Not a frozen pane: the world was changing the whole time, the state
file was being rewritten in step with the transcript, and none of it reached
the screen because renderFrame sends an idle session to renderIdleFrame
before anything else gets a say.
SessionIdle is a hint. The transcript raises it off
{"type":"system","subtype":"turn_duration"}, whose meaning is "that turn
ended", and parse.go has always said as much in a comment. The machine
treated it as a state, and the only events that cleared it were SessionStart,
UserPromptSubmitted and PostCompact. An interactive tab gets one of those
with every prompt, so the hint expires on its own and nobody noticed it could
not expire by itself. A background job session never types a prompt. Its work
arrives as a job, so after the first turn ended it latched idle for good.
Captured on a live session: SessionIdle at 19:51:13, then eight tool calls
between 19:52:19 and 19:53:24 with the pane still on the idle screen, then
the next turn_duration at 19:53:37.
So a tool call, a spawn or a permission prompt now clears it. Those are work
by definition; tokens and thinking are not, because they also arrive while a
turn is winding down and the difference between "still finishing" and
"started again" is the whole question here. The next turn_duration idles the
session again, so this expires a stale hint rather than pinning the session
active. SessionEnd stays terminal and a straggler call cannot resurrect it.
The timestamp guard is what keeps this from fighting a real idle. The
transcript runs seconds behind and reads its files one after another, so an
agent file catching up delivers tool events stamped minutes before the turn
end that has already been applied. Those are the tail of a turn that is over,
not evidence of a new one, and acting on them would flip the pane out of the
idle screen every time a slow file landed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A pane attached to a background job session drew the idle screen, "watching for activity", over a main agent that was starting a Bash call every few seconds.
Not a frozen pane. The world was changing the whole time and the state file was being rewritten in step with the transcript; none of it reached the screen because
renderFramesends an idle session torenderIdleFramebefore anything else gets a say.Why it latched
SessionIdleis a hint. The transcript raises it off{"type":"system","subtype":"turn_duration"}, whose meaning is "that turn ended", andparse.gohas always said so in a comment. The machine treated it as a state, and the only events that cleared it wereSessionStart,UserPromptSubmittedandPostCompact.An interactive tab gets one of those with every prompt, so the hint expires on its own and nobody noticed it could not expire by itself. A background job session never types a prompt. Its work arrives as a job, so after the first turn ended it latched idle for good.
Captured on a live session, tracing main's events through the machine:
What clears it
A tool call, a spawn or a permission prompt. Those are work by definition. Tokens and thinking are not, because they also arrive while a turn is winding down, and the difference between "still finishing" and "started again" is the whole question here.
The next
turn_durationidles the session again, so this expires a stale hint rather than pinning the session active.SessionEndstays terminal and a straggler call cannot resurrect it.The timestamp guard
The transcript runs seconds behind and reads its files one after another, so an agent file catching up delivers tool events stamped minutes before a turn end that has already been applied. Those are the tail of a turn that is over, not evidence of a new one, and acting on them would flip the pane out of the idle screen every time a slow file landed.
Tests
Four cases in
internal/state/machine_test.go: work after a turn end leaves idle and the next turn end re-idles; a late event predating the turn end does not; tokens and thinking alone do not; an ended session stays ended.Independent of #7, which is about which session a pane attaches to. This one is about what it draws once attached. The three
codexsrcTestRealCodexSession*failures predate both and fail onmain.