Skip to content

fix(agent-status): a Claude background workflow counts as live work from its launch - #24214

Closed
brennanb2025 wants to merge 6 commits into
brennanb2025/claude-background-shell-recordfrom
brennanb2025/claude-background-workflow-record
Closed

brennanb2025 wants to merge 6 commits into
brennanb2025/claude-background-shell-recordfrom
brennanb2025/claude-background-workflow-record

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 23 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​958 $\color{#cf222e}{\Huge{\mathbf{−}}}$​175 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​783
Prod 8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​221 $\color{#cf222e}{\Huge{\mathbf{−}}}$​52 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​169

ELI5

The bug. Claude's Workflow tool runs a script of agents, and it always runs in the background. In a Claude terminal pane, if you press Ctrl+C in the turn that launched a workflow, the workflow keeps running, but whenever its script is waiting between two agents, Orca shows the pane done, interrupted. orca worktree ps says done, and Auto keep-awake lets the laptop sleep in the middle of the workflow.

How long. The wait is whatever the script waits. Captured: 20 s and 60 s with no hook and no transcript line at all; there is no limit.

Why it happens. After a Ctrl+C, Claude sends no hook and no end-of-turn Stop (so no task list either). #24116 (this PR's base) records a background task from the tool call that launches it, but it reads only a shell's field, tool_response.backgroundTaskId. A workflow's launch names its task tool_response.taskId, with status: "async_launched" and taskType: "local_workflow", so #24116 never records it. The only thing that kept the pane live was whichever of the workflow's agents happened to be running.

What you see after this PR. After a Ctrl+C in the launch turn, the pane reads Working while one of the workflow's agents runs and Monitoring while none does, then done within about a second of Claude writing the workflow's end line to its transcript.

Where you will notice it. Only after a Ctrl+C in the launch turn, while none of the workflow's agents runs. A workflow that runs to completion, a one-agent workflow Ctrl+C'd in its launch turn, and a workflow across /clear show no visible change: the only difference there is a sub-second blip at the very end.

One fewer notification. Today, when the workflow's agent stops after that Ctrl+C, the pane flips to Interrupted and Orca sends a "Claude stopped" notification in the middle of the still-running workflow. After this PR it doesn't; the only notification is the one when Claude's report turn finishes (live QA: 2 on the base, 1 here).

Keep-awake (Auto mode). Monitoring publishes as state: working with workingMode: monitoring, so a pane in the wait now holds Auto keep-awake, as a Monitoring pane with a running shell already does. Nothing renews that hold on a quiet pane: only an accepted hook renews it (Claude never sends the terminal status sequence that is the other path, and the transcript watch publishes nothing while nothing changes), so it lasts at most 2 h after the last hook.

Open decision: conflict with #22322. #22322 (open, keep-awake per kind) makes a running workflow read Working and stops Monitoring from holding Auto keep-awake. This PR shows a workflow whose launch turn was Ctrl+C'd as Monitoring and relies on Monitoring holding keep-awake, so the laptop does not sleep mid-workflow. If #22322 lands as written after this PR, that workflow would stop holding keep-awake again. Whichever lands second must settle how a running workflow is shown; the recommendation is #22322's rule (a running workflow reads Working and holds keep-awake). This PR changes no code for it.

Scope.

  • Surface: Claude sessions in Orca terminal panes (the hook-driven status path), local, WSL and SSH, and every reader of that row (sidebar, worktree ps, mobile, dashboard).
  • On SSH panes Orca never infers a Ctrl+C (their input is not delivery-confirmed), so SSH reads Working through the whole run before and after this PR; nothing visible changes there.
  • Not affected: panes with no background workflow, Claude chats in Orca's chat view (a separate status path), and every other agent.

The plan. This PR is step 4 of 6, and they merge in this order:

  1. fix(agent-status): reflect Claude's idle-prompt Ctrl+C killing background subagents #22722: Orca tracks Claude's subagents from their own hooks.
  2. refactor(agent-status): one Claude transcript watch per pane, read before every event #24075: one watch per pane on Claude's own session log (its "transcript"), read by the host that runs Claude.
  3. fix(agent-status): a Claude background shell counts as live work from the tool call that starts it #24116 (this PR's base): a background shell counts as live work from the tool call that starts it, and stops counting on Claude's own record of its end.
  4. This PR: a background workflow (and any other non-agent background task Claude launches) counts as live work from its launch, through the same record and the same end edges.
  5. F1 (planned): read Claude's own interrupt notice on every turn (STA-2413), so an Esc, and a stop on SSH, count as cancels.
  6. The Deny/Esc PR (planned): answering a permission prompt with No, or pressing Esc, ends Orca's "needs you" wait.

F1 must not merge before this PR (or must be based on it): once Esc and SSH stops count as cancels, a workflow would read done during every no-agent stretch after them.

What Changed

The launch reader (readClaudeBackgroundTaskLaunch, claude-non-agent-work.ts), for the main agent's own PostToolUse only (a subagent's launch is its own work, as for shells):

  • A shell: tool_response.backgroundTaskId, recorded with kind unknown. Unchanged from fix(agent-status): a Claude background shell counts as live work from the tool call that starts it #24116.
  • Anything else: Claude's background-launch result, status === "async_launched" with a taskId. Its kind comes from taskType through the shared kind table (classifyClaudeBackgroundTaskKind).
  • Agent kinds are skipped: the subagent roster owns agents. Captured background Agent launches carry agentId and no taskId, so they never reach this branch; a future one that names taskType: local_agent would be skipped by the table.
  • Every other kind is recorded, unknown kinds included, since unknown fails toward live. The reader names no kind string.

One admission rule for one fact. Claude's end-of-turn task list (claude-background-task-inventory.ts) already records every non-agent kind, unknown included. The launch reader now admits the same set, so no kind can read live after a normal turn end but done after a Ctrl+C in its launch turn.

The kind table learns the task list's name for a workflow. workflow joins local_workflow as workflow. Without it, a recorded workflow's kind flipped to unknown at the next Stop. No liveness reader reads the kind (the fold reads only whether the record is non-empty), and the chat view's stream names workflows local_workflow, so nothing it shows changes. This is the table's only change; teammates are left alone (see Review).

The record keeps the launch's kind. recordClaudeNonAgentTaskLaunch takes the launch's kind; a kind a task list already gave the task wins.

Nothing else changes. Every way #24116's record ends a task applies to workflows unchanged: Claude's queue-operation end line matched by both the task id and the launching tool call's id (the captured workflow end lines carry both, equal to the launch's), a TaskStop result, the next main-agent task list, a new Claude process, and the pane's process, terminal or relay ending.

Comments and doc. #24116's shell-only wording that this makes false is rewritten, notably that a /tasks stop always writes an end line: it does for a shell, not for a workflow stopped or paused from /tasks, which leaves on the next task list. docs/reference/agent-status-store.md (tracked) states the launch shapes, the shared admission rule and that exception.

Tests. The capture replay helpers move from #24116's shell test into claude-background-task-capture.test-fixture.ts, which both the shell and the new workflow stories import.

Why

Root cause over patch. The workflow is the live thing; its agents come and go inside it. Recording the workflow from its launch, through the record and end edges #24116 already built, fixes the model instead of patching the between-agents window. It adds no new state, no new end reader and no wire change.

Alternatives considered:

  • Admit only taskType === "local_workflow": a string special case, and it would disagree with the task list, which already admits every non-agent kind. That disagreement is this bug again for other kinds.
  • Read Claude's workflow state file (<session>/workflows/<runId>.json) to learn a stopped or paused workflow: a second end reader for one kind; no payload names the file; it moves to the new session on /clear while the launch still names the old one; every captured copy is written only at the end.
  • Read the workflow agent's own transcript for its interrupt line: an interrupted agent is not proof the workflow ended.
  • A timer on a quiet workflow: it re-derives nothing, and it would hide a long wait, which is exactly this bug.
  • Probe processes: a workflow runs inside Claude's process, with no process to map.
  • Fold this into fix(agent-status): a Claude background shell counts as live work from the tool call that starts it #24116: fix(agent-status): a Claude background shell counts as live work from the tool call that starts it #24116 is reviewed and complete for shells, and behaves exactly like its base for workflows. Stacking keeps a finished fix separate from this PR's open live-QA gate.

The stack is not a partial design. #24116 alone is the right design for shells; this PR widens what it admits and undoes nothing. What lands after both is what one would write from scratch.

Wire compatibility. No wire change. The reader is in shared code that the SSH and WSL relays run, and the relay publishes the Monitoring row shape it already publishes for shells, which older desktops render.

Differences from the reference implementations, labelled:

  • The start is the launching tool call's result (taskId + taskType), not a task-start event from a streaming session. Intended: the hook path has no start event, and the launch ids reappear in Claude's end line and task list.
  • Admission is every async launch whose kind the table does not call an agent, unknown kinds included. Intended: the same rule as Orca's own task list reader and chat-view tracker; agents are carved out because Orca's subagent roster owns them.
  • A teammate launch that names a taskId would be admitted as unknown. Temporary: the shared table maps in_process_teammate to unknown; the follow-up maps it to an agent with a test for the chat view, which shares the table.
  • A workflow stopped or paused from /tasks leaves only on the next task list (the references retire on a terminal status update). Temporary: Claude writes nothing Orca can read for it; see Review for the follow-up and its exit.
  • The end is Claude's end line matched by both recorded ids. Intended (inherited from fix(agent-status): a Claude background shell counts as live work from the tool call that starts it #24116).
  • Nothing is settled at an interrupt. Intended (inherited): Orca does not own the Ctrl+C, and Claude keeps the workflow running.
  • The record lives in the hook listener, not the shared child-work model. Temporary (inherited from fix(agent-status): a Claude background shell counts as live work from the tool call that starts it #24116): it moves with the host-owned store work.

Linked Issue

Fixes STA-3344 (#12382).

Live QA (below) shows the base (#24116) reading done/Interrupted and this PR reading Monitoring through a 60 s script wait between two agents, after a Ctrl+C in the launch turn.

#12382's measured case, an interrupted Stop whose task list names a running workflow, already reads live on main: the task list sets the running-task fact with no interrupt argument, and the status fold does not consult how the turn ended. Current Claude sends no Stop at all on Ctrl+C, so the reporter's steps now reach the launch-only path, which is what this PR covers.

Does not fix: #10997 (approvals and plans), STA-2413 / #10114 (F1, the interrupt notice), #21327 (a subagent killed without SubagentStop pinning Working, the class the paused-workflow case below belongs to).

Related: #24116, #24075, #22722, #22322 (keep-awake per kind).

Visual Proof

Live QA on a remote Mac (M4Air) with real Claude Code 2.1.285 in an isolated home, a hidden dev Orca per build, driven over the DevTools protocol, status sampled every 100 ms. Base = #24116 (production-identical a3d97b99ddf); head = this PR at 70cc59395f0 (later commits change only tests and comments). Each Ctrl+C was pressed while agent 1's sleep ran, more than 1 s from any hook, so the renderer's 500 ms settle window could not drop it.

Scenario Base (today) This PR Notifications base / PR
Q1 Ctrl+C in the launch turn, then a 60 s script wait between two agents Interrupted from agent 1's stop through the whole wait while the workflow ran; "Claude stopped" 1.5 s into the wait Monitoring through the whole wait; Working for agent 2; Claude's report turn starts 0.08 s after the end line; Done 2.7 s after it 2 / 1
Q2 one-agent workflow, launch turn ends normally Working, report turn, Done Same 2 / 2
Q3 Ctrl+C in the launch turn, then the workflow stopped from /tasks during the wait (known temporary gap) Interrupted from agent 1's stop; an extra "Task complete" notification at the /tasks stop Monitoring for the 2 min 12 s watched, with nothing running; the next prompt cleared it 3 / 1
Q1 Base This PR
Just after Ctrl+C (agent 1 running) base q1 working after ctrl+c head q1 working after ctrl+c
+30 s into the 60 s wait base q1 wait +30s: Interrupted head q1 wait +30s: Monitoring
+30 s, sidebar base q1 +30s sidebar: red Interrupted dot head q1 +30s sidebar: Monitoring background tasks
After the workflow ended base q1 ended: Done head q1 ended: Done
Q2 Base This PR
Running base q2 running head q2 running
After it ended base q2 after head q2 after
Q3 (this PR)
+120 s after the /tasks stop: still Monitoring head q3 stop +120s
After the next prompt: Done head q3 next prompt settled

Seen on both builds, not caused by this PR: a working row's age label counts from the previous Done (it read "2m" 20 s into Q3's Monitoring), and about 1.3 s of Monitoring between a normal launch turn's Stop and the workflow agent's first hook.

Testing

New capture fixtures (src/shared/__fixtures__/claude-background-workflow-*-hooks.jsonl, sidecars beside them), recorded from Claude Code 2.1.285 over a real terminal, in #24116's format:

  • …-script-wait-ctrl-c (W8, from r8-w8-run2): two agents with a 60 s script wait, Ctrl+C in the launch turn. The discriminating fixture.
  • …-ctrl-c (W2): one agent, Ctrl+C in the launch turn; keeps Claude's interrupt notice line as a line that is not an end line.
  • …-completed (W1), …-clear (W4), …-script-wait (W6a, 20 s wait, normal launch turn), …-idle-ctrl-c (W5a), …-tasks-kill (W3), …-pause (W6b).
  • Scrub: every UUID (session, prompt, message, turn and row ids, and those inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, <output-file> and <diagnostics>) maps to a placeholder distinct per fixture; each workflow agent id to a same-shape placeholder; msg_ ids to a same-length placeholder; task ids, tool call ids and wf_ run ids stay verbatim. The capture scanner reports only the placeholder UUIDs; a grep for the machine's user, host and account names finds none.
  • Write order is taken from Claude's own line timestamps, not the fixture's t for a transcript line (the rig's later poll time): in W1, W4, W6a and W8 the end line precedes the report turn's prompt hook by 40–43 ms.
  • W8 uses run 2 of two identical runs: in run 1 agent 1's first hook landed 0.487 s after the key, 13 ms inside the renderer's 500 ms settle window, where the renderer most likely drops the inferred Ctrl+C, so run 1 most likely cannot show the base's done.

New tests, and what each reads on #24116's head (production files at 18047aa61a3, tests kept: 13 of the new tests fail there, each for the reason below):

  • W8: after agent 1 stops, Monitoring (main agent cancelled) through the 60 s wait, proven on a watch tick late in the wait; Working for agent 2; done, interrupted at the end line; the report turn is its own turn. Base: done at agent 1's stop, the start of the wait.
  • W2: Monitoring after the agent's stop until the end line; Claude's interrupt notice line changes nothing. Base: done at the agent's stop.
  • W1: done at the end line, before the report turn opens, repeating the launch turn's completion time (so no second completion). Base: Monitoring until the report turn's Stop.
  • W4: the workflow is kept across /clear and settles on the end line in the new transcript with the pre-/clear completion time. Base: Monitoring after the end line.
  • W6a (guard): Monitoring through the 20 s wait after a normal launch turn on both builds; settles on the end line. Base: fails only at the end line (Monitoring until the Stop).
  • W5a (guard): an idle-prompt Ctrl+C infers nothing and changes nothing; the pane reads done on the workflow's end line. Base: fails at the end line.
  • W3 (pins the temporary residual; also a guard against a quiet-workflow timer): after a /tasks stop, no hook and no line for 135 s; still live on a watch tick 1 s before the next prompt; that prompt's empty task list clears it. Passes on both builds.
  • W6b (pins the temporary residual): a pause kills the agent with no SubagentStop; Working until the next prompt's empty task list. Passes on both builds.
  • Relayed pane (WSL's local relay), W2: the relay's record keeps the desktop's cancelled pane on Monitoring after the agent stops, 32 s after the Ctrl+C and long past the desktop's 15 s cancel window, and the end line the relay reads settles it done, interrupted. Base: done at the agent's stop.
  • Launch reader (unit): a captured workflow launch records kind workflow; a shell still records from backgroundTaskId; remote_agent, monitor_ws and a missing taskType record as unknown; in_process_teammate records as unknown (labelled temporary); the captured background Agent launch and agent-kind taskTypes record nothing; a taskId without async_launched records nothing; a subagent's workflow launch records nothing; an earlier task list's kind wins. Hand-built rows are labelled as such.
  • End-line parser (unit, hand-built from the captured workflow end line): a <result> that contains a <task-notification> literal is refused (fails toward live); a <result> naming another recorded task's ids retires only the task the line names.
  • Task list (unit): W1's captured Stop entry types the workflow as workflow. Base: unknown.

Ablations (one production change reverted at a time, the five named test files, restored from HEAD after each):

  • Drop the async-launch reader: 12 fail (every workflow story except the two residual pins, the relayed story, the parser test and the launch unit tests).
  • Drop the async_launched check: 1 fails ("ignores a taskId that is not an async launch").
  • Admit agent kinds: 1 fails ("leaves agents to the rosters").
  • Drop the workflow table entry: 1 fails (the task list unit test).
  • Ignore the launch's kind: 1 fails ("records a workflow from its async launch, typed from its taskType").
  • Record a subagent's launch: 2 fail (the shell and the workflow "subagent's launch" tests).

Runs: tc-gate pnpm tc:node and tc-gate pnpm tc:cli pass (no renderer file changed; tc:node re-run after the review's test changes). pnpm run check:code-quality:changed: 0 new findings. oxlint on every touched file: clean. Vitest, 104 named files (#24116's list plus the new workflow stories, including orca-runtime.test.ts): 2538 passed, 1 skipped; the 8 files this PR changes pass 97/97 at the head. CI green.

Platforms: macOS vitest, plus the live macOS run above. No Linux, Windows, WSL or SSH run.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Review

Still not fixed, labelled:

Not verified: the 30 min and 2 h limits of the /tasks gap were derived from code, not waited out live; Linux, Windows, WSL and SSH; teammate and Monitor-tool launches (a Monitor launch is not recorded by this PR, the same as its base).

Merge: after #24116, and before F1 and the Deny/Esc PR. Retarget to main once #24116 lands.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

…rom its launch

The launch reader records any main-agent async launch naming a taskId, typed through the
shared kind table (agents stay with the rosters, unknown kinds fail active), beside a
shell's backgroundTaskId. The kind table learns the inventory's 'workflow' name. Stories
from the r6/r7 workflow captures, the replay helpers moved into a shared test fixture,
and the shell-only comments and doc lines rewritten.
…in its launch turn

Built from r8-w8-run2: in run 1 the first agent hook landed inside the renderer's 500 ms
settle window, where the inferred Ctrl+C is dropped on every build.
@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Changes how background workflows are tracked as live work.

The PR does not yet appear safe to merge because a stopped or paused workflow can remain falsely active until a later task inventory.

Findings

  1. P1 Stopped workflows remain live. ▶

Summary

This PR records Claude background workflows as live work from their launch, so a cancelled launch turn can show Monitoring between workflow agents. It also adds captured workflow and relay coverage and clarifies the status-store documentation.

  • Completed workflows can settle from their transcript end line.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Launch[Main-agent workflow launch] --> Record[Non-agent task record]
  Record --> Live[Working or Monitoring]
  End[Transcript end line or task inventory] --> Retire[Retire task]
  Retire --> Done[Done when no other work remains]
Loading

Reviews (2) · Last reviewed commit: "test(agent-status): W5a waits on the pan..."

Comment on lines +94 to +95
const kind = classifyClaudeBackgroundTaskKind(response.taskType)
return isAgentChildWorkKind(kind) ? undefined : { taskId: response.taskId, kind }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stopped workflows remain live. If a workflow is stopped or paused from /tasks while it waits between agents after Ctrl+C in its launch turn, this new record keeps the pane Monitoring even though the workflow has ended. Captured stops and pauses produce neither a hook nor a transcript end line, so the record remains until a later turn provides a task inventory. If no later turn arrives, the false working status persists and can hold Auto keep-awake. The end state needs a way to be reconciled without relying solely on the next inventory.

Knowledge Base Used: Agent integrations and hooks

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — the hook-lane record now admits a non-shell background launch so a Claude Workflow counts as live work from its launching tool call, closing the between-agents window after a Ctrl+C in the launch turn.

  • Launch reader: readClaudeBackgroundTaskLaunch handles a shell's backgroundTaskId as before, and otherwise Claude's status: "async_launched" + taskId result, typing it from taskType and skipping agent kinds so the rosters keep owning agents.
  • One admission rule: the launch now admits the same set the Stop inventory already admits (every non-agent kind, unknown included), so a kind can no longer read live after a normal turn end but done after a Ctrl+C.
  • Kind table: workflow joins local_workflow as workflow, covering the inventory's spelling; the launch's kind is kept only when an inventory has not already typed the task.
  • Tests/fixtures/docs: eight new captured workflow stories (W1–W8) replayed through the server's HTTP ingress, launch-reader units, a relayed-pane story, plus a shared capture-replay fixture and the agent-status-store.md rewrite.

Verified via a focused sub-review that the shared-table 'workflow' alias does not change the native-chat / SDK-stream lane (claude-background-task-tracker.ts backgrounded, MATERIALIZED_TASK_KINDS): every in-repo stream frame names local_workflow (already mapped), and bare workflow appears only in the hook inventory this change targets. No anchored or non-anchored actionable findings; the documented /tasks-stop residual and the missing live QA are correctly scoped as future work by the author.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: ready for your review (not merged; auto-merge not enabled)

The problem. If you press Ctrl+C in the Claude turn that launched a background workflow, the workflow keeps running. But whenever its script waits between two agents, Orca shows the pane as done/Interrupted: the sidebar, orca worktree ps and mobile all say done, Auto keep-awake lets the laptop sleep mid-workflow, and a "Claude stopped" notification fires while the workflow is still running. The cause: #24116 records a background task from its launch result, but it reads only a shell's backgroundTaskId, and a workflow's launch names its task taskId (status: "async_launched", taskType: "local_workflow").

What you see after this PR. In that stretch the pane reads Monitoring, then Working when the next agent starts, then Done when Claude's report turn finishes. The mid-workflow "Claude stopped" notification is gone. A workflow with no Ctrl+C in its launch turn looks the same as before.

Live QA (M4Air, real Claude 2.1.285 in an isolated home, base = #24116 vs this PR): Ctrl+C in the launch turn followed by a 60 s script wait reads Interrupted through the wait on the base and Monitoring on this PR, with notifications 2 vs 1. A single-agent workflow whose launch turn ends normally shows no difference. The body now says "Fixes STA-3344 (#12382)", and still notes that #12382's measured case (an interrupted Stop listing a running workflow) already reads live on main.

+30 s into the 60 s wait Base (#24116) This PR
Sidebar base: red Interrupted dot while the workflow runs this PR: Monitoring background tasks

Decision for you: conflict with #22322. #22322 makes a running workflow read Working and stops Monitoring from holding Auto keep-awake. This PR relies on Monitoring holding keep-awake, so the laptop doesn't sleep mid-workflow. Whichever PR lands second must settle how a running workflow is shown. Recommendation: #22322's rule, where a running workflow reads Working and holds keep-awake. This PR changes no code for it.

Fixed in review (tests, comments and docs only; production behaviour unchanged):

  • 1842e8ac31d: the launch reader's comment and agent-status-store.md now say exactly what the reader reads and what it skips (for example, a remote remote_launched launch). The W8 note now says capture run 1's Ctrl+C was "most likely" dropped: its hook landed 13 ms inside the 500 ms settle window, so "on every build" was too strong.
  • ef679ebafb2: the idle-Ctrl+C story now checks what the pane shows, not the stored record; an ablation confirms it still fails without the fix. The relayed-pane test reuses the shared capture helpers instead of its own copies. A test header no longer claims every workflow runs its agents one after another.

Deferred, labelled temporary in the body:

  • A workflow stopped or paused from /tasks while none of its agents runs, after a Ctrl+C in its launch turn, reads Monitoring until your next prompt. Confirmed live for 2 min 12 s. The sidebar drops it after 30 min without a hook, and keep-awake releases at most 2 h after the last hook; nothing renews it. Follow-up: one ticket for the stop and the pause cases, not filed.
  • A teammate launch carrying a taskId would read unknown and stay live until the next Stop (uncaptured). Follow-up: fix the shared table in its own PR, with a chat-view test.
  • P3s with no user-visible effect: the once-a-second transcript check runs for a recorded workflow's whole life (same cost fix(agent-status): a Claude background shell counts as live work from the tool call that starts it #24116 pays for shells); the W6a replay clock runs 74 ms backwards (no assertion depends on it); minor test titles and labels.

Found in review, not caused by this PR:

  • A Working or Monitoring row's age label counts from the previous Done ("2m" 20 s into Monitoring). It shows on both builds and dates to feat(dashboard): open-worktree dialog control + finished-time parity #9686.
  • A Monitor-tool launch is not recorded, so after a Ctrl+C in its launch turn it reads done on the base and on this PR alike.
  • The base sends an extra "Task complete" notification at a /tasks stop with no turn running; this PR does not.

Verified:

  • It works with real Claude's hooks: all 13 raw workflow captures (Claude 2.1.285) carry the launch shape the reader reads, and the fixtures rebuild byte-for-byte from those captures.
  • Main has not landed an equivalent fix, and no open PR implements it.
  • Tests: the 8 changed test files pass 97/97, and the builder's run of 104 named files passed 2538.
  • tc:node and tc:cli pass, the changed-lines quality gate reports 0 new findings, and CI is green at ef679ebafb2.
  • The readiness checklist has no P0 or P1.
  • Your real Claude settings on the M4Air were unchanged throughout (hash 090fe2f8ba3527da).

Not verified: Linux, Windows, WSL and SSH runs (SSH never infers a Ctrl+C, so nothing visible changes there); the 30 min and 2 h limits (derived from code, not waited out live); teammate and Monitor-tool launches. The QA seat was Claude, because Grok's usage balance was exhausted.

Merge order: after #24116, before F1 and the Deny/Esc PR. Retarget to main once #24116 lands.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing this older workflow patch. Rebuild workflow lifetime together with the sleep rule so a cancelled foreground reply cannot hide a workflow that is still running; that work remains tracked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant