feat(claudecode): hold working while async agents/workflows are pending #161
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!161
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/159"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #159
What
The claude-code adapter now derives
working— notneeds_input— when the model ends its turn while async background work it spawned (Agent tool launches, Workflows) is still running, so the push gate never fires a spurious "needs input" notification mid-run. The CLI re-invokes the model when the work completes; only the run's real turn-end pushes.How
Truth source is structural harness markers, never message prose (as grilled in the issue):
foldTranscript(internal/provider/claudecode/chat.go): the top-leveltoolUseResult.status == "async_launched"on a launch's tool_result event addsagentId(Agent) /taskId(Workflow — the id notifications actually reference, never thewf_…runId). The same gate structurally excludes background Bash (backgroundTaskId, no status — a dev server never exits and would pinworkingforever), Monitor, and synchronous Agent calls (status:"completed").<task-notification>payloads (<task-id>+ any non-empty<status>: completed/failed/killed) via all three real carriers —queue-operationcontent, the standalone user message form, and the mid-turnattachment(commandMode:"task-notification"). Status-less Monitor<event>payloads never remove. Removal is idempotent (duplicate notifications per id are real).TaskStopremoves byinput.task_id; SendMessage'sresumedAgentIdre-adds (messaging a stopped agent live-verifiably resumes it and re-notifies on the same id).deriveState:assistant:text+ pending non-empty →working; every notification carrier itself derivesworking("about to be re-invoked", closing the enqueue→delivery window); API errors stayneeds_inputunconditionally; the structured break-through (live dialog spool →question, blocked marker →needs_input) is untouched and pinned by a new ReadChat test.Fixture spike (plan step 1)
No async fixtures existed. Marker shapes were pinned by live probes on the installed CLI 2.1.206 (async Agent launch, Workflow, background Bash, TaskStop→killed notification, SendMessage resume with duplicate notification) and cross-verified against wild transcripts from real lab sessions on 2.1.198/2.1.204 (~1,440 status-bearing notifications surveyed; shapes identical across versions, delivery form varies by session state only).
Tests
claudecode: table-driven state edges + cumulative-prefix trajectory tests (launch→text holds; notification→resume→final text = one real needs_input; Bash never holds; sync Agent never holds; failed/killed release; status-less payloads don't; TaskStop releases; SendMessage re-arms; error outranks the hold; spool dialog/blocked marker break through).compat: three captured roundtrip fixtures (transcript-async{agent,workflow,bash}-live-*.jsonl) +TestCompat_AsyncTaskFixturesas the version-drift canary; compat.md §5 gained the full coupling subsection (marker shapes, carriers, ordering hazards, re-verify instructions).go vet,gofmt,go build, fullgo test ./...green (only the pre-existing sandbox-environmentalinternal/tmuxxintegration failures, identical on a clean tree).🤖 Generated with Claude Code
https://claude.ai/code/session_011LF7n7JiCQichBw3ZbZyae