Repository navigation
fix(automations): settle a run on the agent's own done when its screen never reads idle - #34096
Conversation
…n never reads idle A Claude Code that negotiates OSC 7501 drops its title glyph, so after a stream gap or a runtime restart main holds no rest evidence for the finished pane and tui-idle never settles. Runs then sat dispatched for 6h. A turn-end done reported after dispatch that has stood for 30s, with no row of the pane saying otherwise, now completes the run; a blocked screen still resolves first. Fixes #34064
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 3 minutes. View limit details
Walkthrough
Priority: Severity of issue fixed: Medium Merge Risk:
|
There was a problem hiding this comment.
🧹 Nitpick comments: 1
src/main/automations/runtime-terminal-run-observer-evidence.test.ts: 1
260-273:Trivial · Functional Correctness · Quick win Assert before the 30-second hold, then confirm completion.
The test currently checks only 20 seconds after
pane.report(). Extend that check to 29 seconds. Do not require the run to remain unsettled when the hold reaches 30 seconds, because completion is permitted at that boundary.Suggested fix
- await vi.advanceTimersByTimeAsync(20_000) + await vi.advanceTimersByTimeAsync(29_000) expect(run.settled).not.toHaveBeenCalled()

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
23485c5c-6fa8-48fc-9b03-121cdcb4cf08
Files selected for processing: 4-
src/main/agent-turn-idle-verdict.tssrc/main/automations/automation-run-agent-evidence.tssrc/main/automations/runtime-terminal-run-observer-evidence.test.tssrc/main/automations/runtime-terminal-run-observer.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.
-
ELI5
Automations that start Claude could finish their work and still never be marked done. Orca waited for the terminal screen to look idle, and on a headless host the screen sometimes never did. Now, if the agent itself reported that its turn ended after the run started, and that report has held for 30 seconds, the run is marked complete.
What Changed
Before: a terminal-session automation run completed only when
tui-idlewas satisfied and adonestatus row newer than the dispatch existed. Iftui-idlenever settled, the run stayeddispatchedfor 6 hours, endeddispatch_failed, and left itsclaudeprocess running.After: while the run observer waits for
tui-idle, it re-checks every 30 seconds. If the pane's status rows show a turn-enddone(not a session-startdoneand not a resume-only row) reported after the dispatch, the done has held for at least 30 seconds, and no row of the pane says the agent is working or waiting, the run completes. A screen that reads as blocked (for example Codex's plan menu after its Stop hook) still resolves the wait first, so those runs are still reported as blocked. Runs with no agent status at all keep the old behaviour.Mechanism, from a captured Claude Code 2.1.310 session: when the terminal answers Claude's OSC 7501 query (the daemon does this), Claude stops putting
✳in its title and reports rest only through OSC 7501. After a stream gap (the daemon thins hidden or background streams, which is the reporter's "hidden-delivery gate is dropping bytes" log), main ends the program activity and drops its at-restdone. A runtime restart has the same effect. The pane then has a glyph-free title such asDone, no title status, and no first-party rest, sotui-idlecan never settle on that pane. I reproduced this in the runtime with the captured bytes: the wait settles beforenotePtyDataGapand times out after it.Files:
src/main/automations/runtime-terminal-run-observer.ts,src/main/automations/automation-run-agent-evidence.ts, andsrc/main/agent-turn-idle-verdict.ts. The last one now exports the existing "turn done since the prompt" rule so both checks use the same rule.Why
The Stop hook is the agent's own statement that its turn ended. The screen is a heuristic that a lost stream, a restart, or a new idle screen can break. Letting a held hook
donesettle the run covers every way the screen can go unreadable, for every agent with hooks, not only this one Claude path. The 30-second hold means short races, such as Codex painting its plan menu about 750 ms after Stop or a Stop-hook continuation flipping the row back to working, are decided by the existing signals first.I considered making Claude's hook
doneauthoritative insidetui-idleitself. That would change behaviour for orchestration mail delivery and everyorca terminal waitcaller, and Claude was kept out of the hook lane on purpose (task wake-ups, #24878). This change is limited to automation runs.orca terminal wait --for tui-idleon a pane that lost its OSC 7501 state can still time out; that is a possible follow-up.No wire or protocol change. The observer runs on the host that owns the automation, so SSH, paired clients and older clients are not affected. It does not depend on worktrees, so folder workspaces behave the same.
Linked Issue
Fixes #34064
Thanks to @spaceshipmike for the detailed report, especially the Stop/done evidence in
last-status.jsonand the hidden-delivery log lines that pointed at the stream-gap path.Visual Proof
N/A: no UI change. The change affects when a background automation run is recorded as complete.
Testing
Added an observer test: the pane never reads idle, and the agent reports
doneafter dispatch. The run completes once the done has held 30 seconds, and not before. On the base commit this test hangs.Replayed captured Claude Code 2.1.310 PTY bytes, with OSC 7501 answered, through the runtime's transcript harness. Before a data gap
tui-idleis satisfied; afternotePtyDataGapit times out. This was a probe and is not checked in.pnpm tc, the full static job's lint, format, ast-grep, anti-slop, code-quality and ratchet checks, and every test file that imports a changed module. The only failures were rootoxlintmax-lines errors in files this PR does not touch, which are also on base.Not run: a real headless
orca serveunder launchd on macOS.I manually tested these changes locally
Automated tests added/updated, or explained why not below
AI Disclosure
Review
Agent skill upstream boundary
Notes
Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance
In the judged path the observer now re-arms its wait every 30 seconds instead of every 5 minutes. Each re-arm is one waiter registration and is cancelled cleanly on timeout.
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)