Repository navigation
fix(chat): a turn that ended while its work runs on shows an hourglass and Send, not a live turn - #184
Merged
Conversation
|
@Stener1 is attempting to deploy a commit to the seanbrydonio's projects Team on Vercel. A member of the Team first needs to authorize it. |
Stener1
marked this pull request as ready for review
October 23, 2026 08:41
sean-brydon
approved these changes
Oct 23, 2026
…s and Send, not a live turn Turn check read a reply that reports how another session is getting on and offers to follow it up as working, so a finished session stayed "running": its chat kept the live turn's spinner word, a ticking timer, Stop and queue mode while the person typed their next message. - Turn check's prompt: a status report on another session or worker with an offer is done; waiting on another session is working only when the agent says it will act when that session reports back. Real turns are replayed through recorded AI Gateway answers (testdata/turncheck), each tied to its exact request, so a prompt change asks for a new recording. - Claude Code marks a subagent's tool hooks with agent_id. The adapter passes that on, and the ledger ignores a helper's tool use while no turn is open, so a helper left running in the background no longer puts a finished session back to running. The agent's own tool use with no turn open (a prompt Claude Code took from its queue) still starts one. - The chat draws a session on its work (finished + unfinished) as between turns: an hourglass row, "Waiting on its work" with the dashboard card's tooltip, Send, no Stop. sessionState() is unchanged elsewhere. - verify-berthd: chat-turn-state feature and scenario. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed for Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… notes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… shows the hourglass, a live turn Stop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's handoff wording (cosscom#183) changed the request these six replays were recorded for. Recorded again against the real provider; every verdict is the one it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stener1
force-pushed
the
chat-turn-ended-state
branch
from
October 23, 2026 23:06
de46a27 to
d30dbd5
Compare
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.
Summary
A finished turn that turn check read as working kept the chat in live-turn mode: the spinner's fallback word ("Making way… · 49s"), Stop, and queue mode, while the person typed their next message. The reply that triggered it reported on another session and offered a follow-up ("I can nudge it… or tell you when its turn ends").
Diagnosis from the box's journal:
agent.finished(seq 82707), then turn check'sagent.readywithchoice: "working"2 s later. No tool event came between the Stop and the next prompt, so it was the classifier (A). A stray tool event after Stop (B) is real too, though: the journal holds 559 tool events after a Stop. The ones followed by nothing until the next prompt come from background subagents, whose hooks carry the parent'ssession_id.onItsWorkandON_ITS_WORKmatch #183's definitions line for line, so whichever PR merges second has an easy conflict. #183 also changes the same turn-check prompt, so whichever lands second needs to merge both prompts and re-recordinternal/box/testdata/turncheck(the command is below).Evidence
Turn check, the incident turn replayed on a real box (
turn-check-replay.shfrom #183, exact prompt and reply):agent_state=finished unfinished=True, journalagent.ready choice=working.agent_state=finished unfinished=False, twice; turn check publishes nothing.berthd hook: Stop →finished; a PostToolUse withagent_id→ stillfinished, and it isn't journaled; the agent's own PostToolUse →running.Recorded AI Gateway answers (
TestTurnCheckReplays). Each recording is tied to its exact request by a sha256, so a prompt change fails the test until it is recorded again. CI never calls the gateway.Recording again:
BERTH_TURNCHECK_RECORD_KEY_FILE=<AI Gateway key file> go test ./internal/box -run TestTurnCheckReplays.Helper tool events (
turns_test.go):TestAHelpersToolAfterStopLeavesTheTurnFinishedfails on main ("a helper's tool use after the turn ended would be published") and passes here.TestAHelpersPermissionAfterStopEndsWithItsTool: Stop → the helper asks permission (waiting, prompts held) → the helper's tool runs → finished and ready, with no turn made. Turn check's soft wait stays.Chat, real Chromium on the mock (
verify-berthdscenariochat-turn-state, no console errors):Chat on a real box (scenario
chat-turn-state-live: a dev box of this branch, real Claude Code, turn check on). Claude startssleep 600in the background and ends its turn; the box lists itfinished+unfinished. Then a foregroundsleep 45:Asserted: the row text
Waiting on its work, the tooltip = the dashboard card's, aSendbutton with noQueueand noStop …; and in a live turn/…\s*· \d+s/,Stop ClaudeandQueue. The same is inapp/e2e/chat.spec.ts.Checks run locally (fork PR CI waits for a maintainer):
go vet ./...passes.go test -race ./...passes exceptinternal/leadline/api TestDetach, which fails the same way on origin/main (TestServicesAreCountedfailed once under load and passed on rerun).pnpm test(578), and build.Merge Danger
Door: two-way
Prompt text, a ledger guard on events marked
helper, and chat UI. No data written. Revert to undo.Blast Radius: small
parent_tool_use_id.🤖 Generated with Claude Code