fix: keep the turn open while a command runs in the background - #23
Merged
sleeyax merged 7 commits intoSep 9, 2026
Merged
Conversation
A command Claude runs in the background is dispatched exactly as an asynchronous agent is: the tool answers the moment the command starts, Claude goes idle, and it is woken by a <task-notification> when the command ends. Only agents were counted, so the Stop hook in between ended the turn — and Paseo reads a session as busy from its open turn and from nothing else, so everything Claude did from the report onwards happened outside any turn, on a session showing as idle. Chasing one: the session shipping remi-plus#731 backgrounded a code review at 21:37, its last agent reported at 21:40, and the turn ended at 21:41:12 with the review still running. The review reported at 21:44:54, woke Claude, and it worked for another fifteen minutes and three more agents while Paseo showed the agent idle the whole way. Claude records the id its report will name in backgroundTaskId beside the launching tool result, and the notification carries it as the <task-id>, so the launch and the report are joined the way an agent's are. The command has no card — its tool call is the launch, which finished — and it shows nothing at all while it runs, its output going to a file whose name only the report carries. Its bound is therefore a flat thirty minutes from the moment the turn was held rather than a silence, restarted whenever another command is started or reported: of the background commands that have reported across this box's transcripts the median took four minutes and the longest genuine wait was a twenty-one minute code review, while the ones that ran for hours were servers and poll loops, which never report at all and are what the bound is there to let go of. Each kind of work waited on is bounded on its own and the turn ends only once every one of them has run out, so a command's thirty minutes are not cut short by the agents beside it having gone quiet first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CbhLTXNiW7pV8oEhz76gp
sleeyax
force-pushed
the
fix/claude-tty-background-commands
branch
from
September 9, 2026 18:25
885d7a3 to
a48cde0
Compare
The wake bound was given only to a turn with nothing outstanding left, and a command's report stamps an activity the agents' bound does not read. So a turn whose agent had already run its fifteen minutes out ended at the poll after the report Claude had just been woken by, with the answer it was writing landing outside the turn the hold is there to keep it in. The bound now applies whatever is outstanding: no turn ends while Claude is writing, and every kind of work waited on still has to have run its own bound out before one ends at all. An agent that has gone quiet therefore no longer ends a turn Claude is busy in, which is what the test around that case now says. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The log a turn writes as it gives up names the agents it was still holding for and nothing else, so a turn ended on commands that never reported said how many there were and never which. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The name of the file a command writes its output to is in the tool result that starts it, not only in the report it ends with, so the comments and the README saying otherwise were wrong about a file they were right about the treatment of: the adapter does not follow it, which is why a running command shows nothing to measure a silence against and its bound is flat. That reason is now given once, on the constant that holds the bound, and the reason a command is waited on the way an agent is once, at the Stop hook that holds the turn for both; the comments that repeated either say what the thing is instead. The comments this branch wrapped are unwrapped, and the measurements taken off private transcripts are out of the code and attributed in the README. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…a report after the give-up Every command report on this box arrived as a queued command and never as a user turn, which is the shape the translator now has a test for. The runtime has the reverse of the case it already covered: a command whose bound has run out while an agent beside it is still inside its own keeps the turn open. A report that lands after a turn abandoned the command it names moves neither the activity a later bound is measured from nor anything an agent's id would. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A `TaskStop` names an agent or a background command through the one `task_id`, and only the agent was read out of it, so a stopped command was waited on for the whole thirty minutes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
It is checked first and unconditionally, so it no longer gates the last agent to report alone. The test that covers it is given room to breathe under parallel load. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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 session that backgrounds a command shows in Paseo as idle while it goes on working. Found on a live session of mine this evening, and it is the one launch kind the subagent hold does not cover.
Stacked on #22, which is stacked on #21, #20 and #19, so this branch carries all four and the diff against
mainincludes them. The commit to read here is the last one.What was happening
A background command is dispatched exactly as an asynchronous agent is — the tool answers the moment it starts, Claude goes idle, and a
<task-notification>wakes Claude when it ends — but only agents were counted, so theStophook in between ended the turn. Paseo reads a session as busy from its open turn and from nothing else, and nothing can reopen an ended one.From the session shipping
remi-plus#731, all times local:Stop→ turn heldBash(run_in_background)"Run codex reviewer" startedStop→finishTurn(end_turn), Paseo goes idletranscript-translator.tssaid as much in as many words: "A notification for a background command rather than an agent names no card here, and is left alone."What changed
backgroundTaskIdbeside the launching tool result, and the notification carries it as the<task-id>, so the launch and the report are joined the way an agent's are.TaskNotification.agentIdis nowtaskId, which is what the tag is called and what it holds: an agent's id or a command's.Not covered
KillShellis not distinguished. If no notification follows one, the turn waits its bound out rather than ending at the kill — no transcript on this box has aKillShellin it to read the shape from.Verification
pnpm typecheckandpnpm testat the root: 127 adapter tests, all green, five of them new — the hold across a command's report, the bound and the abandon after it, a command outliving the agents beside it, and the translator's counting, settling and replay.🤖 Generated with Claude Code
https://claude.ai/code/session_016CbhLTXNiW7pV8oEhz76gp