From 7fcfbcc6999ba80034a804b7e7172fab3bc38af8 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 11:50:43 -0700 Subject: [PATCH 1/6] fix(agent-status): a Claude background workflow counts as live work from 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. --- docs/reference/agent-status-store.md | 42 ++- ...de-background-task-capture.test-fixture.ts | 132 +++++++ .../ended-process-reconciliation.test.ts | 7 +- ...r-claude-background-shell-captures.test.ts | 139 ++----- ...laude-background-workflow-captures.test.ts | 351 ++++++++++++++++++ ...er-relayed-claude-background-shell.test.ts | 58 ++- .../server/server-status-update.ts | 2 +- ...aude-background-workflow-clear-hooks.jsonl | 28 ++ ...-background-workflow-clear-hooks.meta.json | 15 + ...-background-workflow-completed-hooks.jsonl | 24 ++ ...kground-workflow-completed-hooks.meta.json | 11 + ...ude-background-workflow-ctrl-c-hooks.jsonl | 27 ++ ...background-workflow-ctrl-c-hooks.meta.json | 11 + ...ackground-workflow-idle-ctrl-c-hooks.jsonl | 25 ++ ...round-workflow-idle-ctrl-c-hooks.meta.json | 11 + ...aude-background-workflow-pause-hooks.jsonl | 22 ++ ...-background-workflow-pause-hooks.meta.json | 11 + ...ackground-workflow-script-wait-hooks.jsonl | 29 ++ ...round-workflow-script-wait-hooks.meta.json | 11 + ...background-workflow-tasks-kill-hooks.jsonl | 20 + ...ground-workflow-tasks-kill-hooks.meta.json | 11 + ...stener-extraction-characterization.test.ts | 7 +- .../providers/claude-events.ts | 23 +- .../providers/claude-non-agent-work.test.ts | 148 ++++++++ .../providers/claude-non-agent-work.ts | 45 ++- .../providers/claude-roster-state.ts | 8 +- .../providers/claude-transcript-watch.ts | 11 +- src/shared/claude-background-task-kind.ts | 4 +- .../claude-background-task-status.test.ts | 17 + 29 files changed, 1079 insertions(+), 171 deletions(-) create mode 100644 src/main/agent-hooks/claude-background-task-capture.test-fixture.ts create mode 100644 src/main/agent-hooks/server-claude-background-workflow-captures.test.ts create mode 100644 src/shared/__fixtures__/claude-background-workflow-clear-hooks.jsonl create mode 100644 src/shared/__fixtures__/claude-background-workflow-clear-hooks.meta.json create mode 100644 src/shared/__fixtures__/claude-background-workflow-completed-hooks.jsonl create mode 100644 src/shared/__fixtures__/claude-background-workflow-completed-hooks.meta.json create mode 100644 src/shared/__fixtures__/claude-background-workflow-ctrl-c-hooks.jsonl create mode 100644 src/shared/__fixtures__/claude-background-workflow-ctrl-c-hooks.meta.json create mode 100644 src/shared/__fixtures__/claude-background-workflow-idle-ctrl-c-hooks.jsonl create mode 100644 src/shared/__fixtures__/claude-background-workflow-idle-ctrl-c-hooks.meta.json create mode 100644 src/shared/__fixtures__/claude-background-workflow-pause-hooks.jsonl create mode 100644 src/shared/__fixtures__/claude-background-workflow-pause-hooks.meta.json create mode 100644 src/shared/__fixtures__/claude-background-workflow-script-wait-hooks.jsonl create mode 100644 src/shared/__fixtures__/claude-background-workflow-script-wait-hooks.meta.json create mode 100644 src/shared/__fixtures__/claude-background-workflow-tasks-kill-hooks.jsonl create mode 100644 src/shared/__fixtures__/claude-background-workflow-tasks-kill-hooks.meta.json diff --git a/docs/reference/agent-status-store.md b/docs/reference/agent-status-store.md index 70273614c5c5..69253a221f2f 100644 --- a/docs/reference/agent-status-store.md +++ b/docs/reference/agent-status-store.md @@ -246,23 +246,31 @@ reader does not mistake them for drift: - The Claude hook lane knows a background shell (or monitor, or workflow) by Claude's task id, in one per-pane record (`claude-non-agent-work.ts`) on the host that runs the session. A task enters it from the main agent's launching - `PostToolUse` (`tool_response.backgroundTaskId`, with that call's - `tool_use_id`) or from the `background_tasks` inventory Claude attaches to - `Stop`, which replaces the record. It leaves on the next inventory that omits - it, on a `TaskStop` result naming it, or on the `queue-operation` `enqueue` - line Claude writes to its transcript the moment the task ends (finished, - killed with its turn's Ctrl+C, or from `/tasks` while Claude idles), read by - the transcript watch below and trusted only when both its task id and its - tool-use id match the launch Orca recorded, since a prompt typed while Claude - is busy writes the same kind of line. A task can end before Orca handles its - launching hook, so its line can precede where the watch reads from; accepting - the launch reads back a bounded tail once for that exact pair. A `startup` or - `resume` `SessionStart` clears the record with the pane's children and crons, - since a new process can never end its predecessor's tasks. `/clear` keeps it: - the shell survives and reports its end in the new session's transcript, and - the pane stays on the tail of the last turn that ended, keeping that turn's - verdict and turn stamp so the shell's end is not announced as a new turn (not - yet after an Esc, which Orca does not infer, so that turn never ended). The + `PostToolUse`, with that call's `tool_use_id`, or from the + `background_tasks` inventory Claude attaches to `Stop`, which replaces the + record. A shell's launch names it `tool_response.backgroundTaskId`; any other + kind's is Claude's `status: "async_launched"` result naming a `taskId` and a + `taskType` (a workflow's is `local_workflow`). The launch admits what the + inventory admits: every kind the shared kind table does not call an agent, + unknown kinds included, since agents belong to the subagent roster. It + leaves on the next inventory that omits it, on a `TaskStop` result naming + it, or on the `queue-operation` `enqueue` line Claude writes to its + transcript the moment the task ends, read by the transcript watch below and + trusted only when both its task id and its tool-use id match the launch Orca + recorded, since a prompt typed while Claude is busy writes the same kind of + line. Claude writes that line for a shell however it ended (finished, killed + with its turn's Ctrl+C, or from `/tasks` while Claude idles) and for a + workflow that completes, but not for a workflow stopped or paused from + `/tasks`, which leaves only on the next inventory. A task can end before + Orca handles its launching hook, so its line can precede where the watch + reads from; accepting the launch reads back a bounded tail once for that + exact pair. A `startup` or `resume` `SessionStart` clears the record with the + pane's children and crons, since a new process can never end its + predecessor's tasks. `/clear` keeps it: the task survives and reports its end + in the new session's transcript, and the pane stays on the tail of the last + turn that ended, keeping that turn's verdict and turn stamp so the task's end + is not announced as a new turn (not yet after an Esc, which Orca does not + infer, so that turn never ended). The pane's process ending, its PTY exiting and the relay stopping clear it with the rest of the pane. The desktop keeps no copy for a relayed pane; it reads the row's `claudeRunningNonAgentTask`, which the relay restates on every row. diff --git a/src/main/agent-hooks/claude-background-task-capture.test-fixture.ts b/src/main/agent-hooks/claude-background-task-capture.test-fixture.ts new file mode 100644 index 000000000000..5644e9aaf4f9 --- /dev/null +++ b/src/main/agent-hooks/claude-background-task-capture.test-fixture.ts @@ -0,0 +1,132 @@ +// Replays the captured Claude background-task stories (src/shared/__fixtures__/ +// claude-background-{shell,workflow}-*-hooks.jsonl) through the server's own HTTP ingress, with each +// hook's `transcript_path` pointed at a temp file. The only record of a task ending with no hook is +// the `queue-operation` line Claude appends to its transcript; a story appends the captured line +// where it was written and lets the listener's transcript watch find it on a real tick. +// Callers mock telemetry and fake only `Date`, and call `cleanUpCaptureReplays` after each test. +import { appendFileSync, mkdtempSync, rmSync, statSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { expect, vi } from 'vitest' +import { AgentHookServer } from './server' +import { buildBody, PANE, postHookEvent } from './server.test-fixtures' +import { + hookAt, + type CapturedRecord, + type CapturedTranscriptScan +} from './claude-cancel-capture.test-fixture' + +const temporaryPaths: string[] = [] +const running: AgentHookServer[] = [] + +export function cleanUpCaptureReplays(): void { + for (const server of running.splice(0)) { + server.stop() + } + for (const path of temporaryPaths.splice(0)) { + rmSync(path, { recursive: true, force: true }) + } +} + +export function temporaryDir(): string { + const dir = mkdtempSync(join(tmpdir(), 'orca-background-task-')) + temporaryPaths.push(dir) + return dir +} + +export function transcriptFile(): string { + const path = join(temporaryDir(), 'session.jsonl') + writeFileSync(path, '') + return path +} + +export async function startServer(): Promise { + const server = new AgentHookServer() + running.push(server) + await server.start({ env: 'production' }) + return server +} + +export function row(server: AgentHookServer) { + const entry = server.getStatusSnapshotForPane(PANE)[0] + if (!entry) { + throw new Error('the pane has no row') + } + return entry +} + +/** The background-task fact the stored row carries (`claudeRunningNonAgentTask`). */ +export function storedTaskFact(server: AgentHookServer): boolean | undefined { + return server._getStateForTests().lastStatusByPaneKey.get(PANE)?.claudeRunningNonAgentTask +} + +export function watch(server: AgentHookServer) { + return server._getStateForTests().claudeTranscriptCursorByPaneKey.get(PANE) +} + +/** The renderer's part: capture the row as the baseline and, once the settle window passes with + * no hook, ask the server to infer from the Ctrl+C. */ +export function pressCtrlC(server: AgentHookServer): boolean { + const baseline = row(server) + return server.inferInterrupt({ + paneKey: PANE, + baselineUpdatedAt: baseline.receivedAt, + baselineStateStartedAt: baseline.stateStartedAt, + baselinePrompt: baseline.prompt, + baselineAgentType: 'claude', + intent: 'ctrl-c' + }) +} + +export function queueLine(records: CapturedRecord[], label: string): CapturedTranscriptScan { + const scan = records.find((record) => record.kind === 'transcript' && record.label === label) + if (scan?.kind !== 'transcript' || scan.lines.length !== 1) { + throw new Error(`Captured transcript line ${label} not found`) + } + return scan +} + +/** The capture's t=0 on the wall clock, from the stamp Claude wrote on its enqueue line. */ +export function captureEpoch(records: CapturedRecord[]): number { + const scan = queueLine(records, 'queue-operation-enqueue') + // JSON.parse returns any; the timestamp is checked by Date.parse. + const parsed: Record = JSON.parse(scan.lines[0]) + return Date.parse(String(parsed.timestamp)) - scan.t * 1000 +} + +/** Replays hooks on the capture's clock, pointing each session's transcript at a temp file. A + * capture with no enqueue line passes its own `t0`. */ +export function replayer( + server: AgentHookServer, + records: CapturedRecord[], + files: Map, + t0 = captureEpoch(records) +) { + return async (indices: number[]) => { + for (const index of indices) { + const hook = hookAt(records, index) + vi.setSystemTime(t0 + hook.t * 1000) + const reported = String(hook.payload.transcript_path) + const transcript = files.get(reported) ?? files.get('*') + await expect( + postHookEvent(server, buildBody({ ...hook.payload, transcript_path: transcript })) + ).resolves.toMatchObject({ status: 204 }) + } + } +} + +/** Appends a captured transcript line at its captured instant. */ +export function writeCaptured( + records: CapturedRecord[], + scan: CapturedTranscriptScan, + path: string +): void { + vi.setSystemTime(captureEpoch(records) + scan.t * 1000) + appendFileSync(path, `${scan.lines[0]}\n`) +} + +/** Waits for a tick to have read everything the transcript holds. */ +export async function watchCaughtUp(server: AgentHookServer, transcript: string): Promise { + const size = statSync(transcript).size + await vi.waitFor(() => expect(watch(server)?.offset).toBe(size), { timeout: 3_000 }) +} diff --git a/src/main/agent-hooks/ended-process-reconciliation.test.ts b/src/main/agent-hooks/ended-process-reconciliation.test.ts index fae2d0e013ff..c79d5005750e 100644 --- a/src/main/agent-hooks/ended-process-reconciliation.test.ts +++ b/src/main/agent-hooks/ended-process-reconciliation.test.ts @@ -97,7 +97,12 @@ describe('reconcileEndedProcessForPaneKeys', () => { const server = await startServer() try { claudeRow(server, 'done') - recordClaudeNonAgentTaskLaunch(server._getStateForTests(), PANE, 'btest0001', 'toolu_test') + recordClaudeNonAgentTaskLaunch( + server._getStateForTests(), + PANE, + { taskId: 'btest0001', kind: 'unknown' }, + 'toolu_test' + ) server._getStateForTests().claudeActiveSessionCronPaneKeys.add(PANE) expect(server.reconcileEndedProcessForPaneKeys([PANE])).toBe(1) diff --git a/src/main/agent-hooks/server-claude-background-shell-captures.test.ts b/src/main/agent-hooks/server-claude-background-shell-captures.test.ts index 06d0ea73a21d..8f143e012a64 100644 --- a/src/main/agent-hooks/server-claude-background-shell-captures.test.ts +++ b/src/main/agent-hooks/server-claude-background-shell-captures.test.ts @@ -5,19 +5,27 @@ // record of a task ending with no hook (a Ctrl+C'd turn's shell, a /tasks kill) is the // `queue-operation` line Claude appends to its transcript; the stories append the captured line // where it was written and let the listener's transcript watch find it on a real tick. -import { appendFileSync, mkdtempSync, rmSync, statSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' +import { appendFileSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { AgentHookServer, _internals } from './server' +import { _internals } from './server' import { buildBody, PANE, postHookEvent } from './server.test-fixtures' +import { cancelLabelled, hookAt, loadCapture } from './claude-cancel-capture.test-fixture' import { - cancelLabelled, - hookAt, - loadCapture, - type CapturedRecord, - type CapturedTranscriptScan -} from './claude-cancel-capture.test-fixture' + captureEpoch, + cleanUpCaptureReplays, + pressCtrlC, + queueLine, + replayer, + row, + startServer, + storedTaskFact, + temporaryDir, + transcriptFile, + watch, + watchCaughtUp, + writeCaptured +} from './claude-background-task-capture.test-fixture' const { getCohortAtEmitMock, trackMock } = vi.hoisted(() => ({ getCohortAtEmitMock: vi.fn(), @@ -27,9 +35,6 @@ const { getCohortAtEmitMock, trackMock } = vi.hoisted(() => ({ vi.mock('../telemetry/client', () => ({ track: trackMock })) vi.mock('../telemetry/cohort-classifier', () => ({ getCohortAtEmit: getCohortAtEmitMock })) -const temporaryPaths: string[] = [] -const running: AgentHookServer[] = [] - beforeEach(() => { _internals.resetCachesForTests() trackMock.mockReset() @@ -40,109 +45,11 @@ beforeEach(() => { }) afterEach(() => { - for (const server of running.splice(0)) { - server.stop() - } - for (const path of temporaryPaths.splice(0)) { - rmSync(path, { recursive: true, force: true }) - } + cleanUpCaptureReplays() vi.useRealTimers() vi.restoreAllMocks() }) -function temporaryDir(): string { - const dir = mkdtempSync(join(tmpdir(), 'orca-background-shell-')) - temporaryPaths.push(dir) - return dir -} - -function transcriptFile(): string { - const path = join(temporaryDir(), 'session.jsonl') - writeFileSync(path, '') - return path -} - -async function startServer(): Promise { - const server = new AgentHookServer() - running.push(server) - await server.start({ env: 'production' }) - return server -} - -function row(server: AgentHookServer) { - const entry = server.getStatusSnapshotForPane(PANE)[0] - if (!entry) { - throw new Error('the pane has no row') - } - return entry -} - -function storedShellFact(server: AgentHookServer): boolean | undefined { - return server._getStateForTests().lastStatusByPaneKey.get(PANE)?.claudeRunningNonAgentTask -} - -function watch(server: AgentHookServer) { - return server._getStateForTests().claudeTranscriptCursorByPaneKey.get(PANE) -} - -/** The renderer's part: capture the row as the baseline and, once the settle window passes with - * no hook, ask the server to infer from the Ctrl+C. */ -function pressCtrlC(server: AgentHookServer): boolean { - const baseline = row(server) - return server.inferInterrupt({ - paneKey: PANE, - baselineUpdatedAt: baseline.receivedAt, - baselineStateStartedAt: baseline.stateStartedAt, - baselinePrompt: baseline.prompt, - baselineAgentType: 'claude', - intent: 'ctrl-c' - }) -} - -function queueLine(records: CapturedRecord[], label: string): CapturedTranscriptScan { - const scan = records.find((record) => record.kind === 'transcript' && record.label === label) - if (scan?.kind !== 'transcript' || scan.lines.length !== 1) { - throw new Error(`Captured transcript line ${label} not found`) - } - return scan -} - -/** The capture's t=0 on the wall clock, from the stamp Claude wrote on its enqueue line. */ -function captureEpoch(records: CapturedRecord[]): number { - const scan = queueLine(records, 'queue-operation-enqueue') - // JSON.parse returns any; the timestamp is checked by Date.parse. - const parsed: Record = JSON.parse(scan.lines[0]) - return Date.parse(String(parsed.timestamp)) - scan.t * 1000 -} - -/** Replays hooks on the capture's clock, pointing each session's transcript at a temp file. */ -function replayer(server: AgentHookServer, records: CapturedRecord[], files: Map) { - const t0 = captureEpoch(records) - return async (indices: number[]) => { - for (const index of indices) { - const hook = hookAt(records, index) - vi.setSystemTime(t0 + hook.t * 1000) - const reported = String(hook.payload.transcript_path) - const transcript = files.get(reported) ?? files.get('*') - await expect( - postHookEvent(server, buildBody({ ...hook.payload, transcript_path: transcript })) - ).resolves.toMatchObject({ status: 204 }) - } - } -} - -/** Appends a captured transcript line at its captured instant. */ -function writeCaptured(records: CapturedRecord[], scan: CapturedTranscriptScan, path: string) { - vi.setSystemTime(captureEpoch(records) + scan.t * 1000) - appendFileSync(path, `${scan.lines[0]}\n`) -} - -/** Waits for a tick to have read everything the transcript holds. */ -async function watchCaughtUp(server: AgentHookServer, transcript: string): Promise { - const size = statSync(transcript).size - await vi.waitFor(() => expect(watch(server)?.offset).toBe(size), { timeout: 3_000 }) -} - describe('Ctrl+C in the turn that launched a background shell (captured, 2.1.284)', () => { const records = loadCapture('claude-background-shell-ctrl-c-hooks') const cancel = cancelLabelled(records, 'S1-ctrl-c-mid-essay') @@ -170,7 +77,7 @@ describe('Ctrl+C in the turn that launched a background shell (captured, 2.1.284 mainAgent: { state: 'done', outcome: 'cancellation' } }) expect(row(server).interrupted).toBeUndefined() - expect(storedShellFact(server)).toBe(true) + expect(storedTaskFact(server)).toBe(true) // The next typed turn's Stop lists the same shell running. await replay([7, 9, 8]) @@ -187,7 +94,7 @@ describe('Ctrl+C in the turn that launched a background shell (captured, 2.1.284 writeCaptured(records, killedAtExit, transcript) await vi.waitFor(() => expect(row(server).state).toBe('done'), { timeout: 3_000 }) expect(row(server).workingMode).toBeUndefined() - expect(storedShellFact(server)).toBe(false) + expect(storedTaskFact(server)).toBe(false) }) it('retires the shell only on a line naming both its task id and its launching call', async () => { @@ -338,10 +245,10 @@ describe('a /tasks kill during a running turn (captured, 2.1.285)', () => { const replay = replayer(server, records, new Map([['*', transcript]])) await replay([0, 1, 2, 3, 4, 5, 6, 7, 8]) expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) - expect(storedShellFact(server)).toBe(true) + expect(storedTaskFact(server)).toBe(true) writeCaptured(records, queueLine(records, 'queue-operation-enqueue'), transcript) - await vi.waitFor(() => expect(storedShellFact(server)).toBe(false), { timeout: 3_000 }) + await vi.waitFor(() => expect(storedTaskFact(server)).toBe(false), { timeout: 3_000 }) expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) // 40 s later the turn's next tool boundary absorbs the notification, and its Stop lists nothing. @@ -419,7 +326,7 @@ describe('/clear while a background shell runs (captured, 2.1.285)', () => { await replay([9]) expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) appendFileSync(cleared, `${completed.lines[0]}\n`) - await vi.waitFor(() => expect(storedShellFact(server)).toBe(false), { timeout: 3_000 }) + await vi.waitFor(() => expect(storedTaskFact(server)).toBe(false), { timeout: 3_000 }) expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) await replay([10]) diff --git a/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts b/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts new file mode 100644 index 000000000000..274172abb0bb --- /dev/null +++ b/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts @@ -0,0 +1,351 @@ +// A Claude background workflow is live work from the tool call that launches it, like a background +// shell. These stories replay hook payloads recorded from Claude Code 2.1.285 over a real PTY +// (src/shared/__fixtures__/claude-background-workflow-*-hooks.jsonl, sidecars beside them). A +// workflow runs its agents one after another, so between them, and before the first, no agent +// holds the pane: after a Ctrl+C in the launch turn only the workflow's own record does. Claude +// writes a completed workflow's end as the same `queue-operation` line a shell gets. +import { appendFileSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { AgentHookServer } from './server' +import { _internals } from './server' +import { PANE } from './server.test-fixtures' +import { cancelLabelled, hookAt, loadCapture } from './claude-cancel-capture.test-fixture' +import type { CapturedRecord } from './claude-cancel-capture.test-fixture' +import { + captureEpoch, + cleanUpCaptureReplays, + pressCtrlC, + queueLine, + replayer, + row, + startServer, + storedTaskFact, + temporaryDir, + transcriptFile, + watch, + watchCaughtUp, + writeCaptured +} from './claude-background-task-capture.test-fixture' + +const { getCohortAtEmitMock, trackMock } = vi.hoisted(() => ({ + getCohortAtEmitMock: vi.fn(), + trackMock: vi.fn() +})) + +vi.mock('../telemetry/client', () => ({ track: trackMock })) +vi.mock('../telemetry/cohort-classifier', () => ({ getCohortAtEmit: getCohortAtEmitMock })) + +beforeEach(() => { + _internals.resetCachesForTests() + trackMock.mockReset() + getCohortAtEmitMock.mockReset() + getCohortAtEmitMock.mockReturnValue({ nth_repo_added: 2 }) + // Why: rows and the cancel latch read Date.now(); watch ticks stay on real timers. + vi.useFakeTimers({ toFake: ['Date'] }) +}) + +afterEach(() => { + cleanUpCaptureReplays() + vi.useRealTimers() + vi.restoreAllMocks() +}) + +function recordedTasks(server: AgentHookServer) { + return server._getStateForTests().claudeNonAgentWorkByPaneKey.get(PANE)?.tasks +} + +function rosterSize(server: AgentHookServer): number { + return server._getStateForTests().claudeSubagentRosterByPaneKey.get(PANE)?.size ?? 0 +} + +function keyAt(records: CapturedRecord[], label: string): number { + const key = records.find((record) => record.kind === 'key' && record.label === label) + if (!key) { + throw new Error(`Captured key ${label} not found`) + } + return key.t +} + +const MONITORING = { state: 'working', workingMode: 'monitoring' } as const +const CANCELLED = { mainAgent: { state: 'done', outcome: 'cancellation' } } as const + +describe('Ctrl+C in the turn that launched a background workflow (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-ctrl-c-hooks') + const cancel = cancelLabelled(records, 'W2-ctrl-c-mid-essay') + const notice = queueLine(records, 'interrupt-notice') + const completed = queueLine(records, 'queue-operation-enqueue') + + it('keeps the pane live after its agent stops, until the line Claude writes when it ends', async () => { + const server = await startServer() + const transcript = transcriptFile() + const replay = replayer(server, records, new Map([['*', transcript]])) + await replay([0, 1, 2, 3]) + // The launch names the workflow by `taskId`; there is no `backgroundTaskId`. + expect(hookAt(records, 3).payload.tool_response).toMatchObject({ + status: 'async_launched', + taskId: 'wuxzs3s3c', + taskType: 'local_workflow' + }) + expect(recordedTasks(server)?.get('wuxzs3s3c')).toEqual({ + kind: 'workflow', + launchToolUseId: 'toolu_01KDk2uKk754Pvw5EpcecXru' + }) + await replay([4, 5, 6, 7, 8]) + expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) + + // The capture: Claude painted "Interrupted", wrote its notice, and sent no hook for 32 s. + expect(cancel).toMatchObject({ interrupted_painted: true, hooks_before_next_typed_prompt: [] }) + vi.setSystemTime(captureEpoch(records) + cancel.t * 1000) + expect(pressCtrlC(server)).toBe(true) + expect(row(server)).toMatchObject({ state: 'working', ...CANCELLED }) + appendFileSync(transcript, `${notice.lines[0]}\n`) + await watchCaughtUp(server, transcript) + expect(row(server)).toMatchObject({ state: 'working', ...CANCELLED }) + + // Its agent's late hooks carry the cancelled turn's prompt id and change only the child work. + expect(hookAt(records, 9).payload.prompt_id).toBe(hookAt(records, 1).payload.prompt_id) + await replay([9, 10]) + expect(row(server)).toMatchObject({ state: 'working', ...CANCELLED }) + await replay([11]) + expect(rosterSize(server)).toBe(0) + expect(row(server)).toMatchObject({ ...MONITORING, ...CANCELLED }) + expect(row(server).interrupted).toBeUndefined() + + writeCaptured(records, completed, transcript) + await vi.waitFor(() => expect(row(server).state).toBe('done'), { timeout: 3_000 }) + expect(row(server)).toMatchObject({ interrupted: true, ...CANCELLED }) + expect(recordedTasks(server)).toBeUndefined() + + // The report turn is a turn of its own. + await replay([12]) + expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) + await replay([13, 14]) + expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + }) +}) + +describe('a background workflow run to completion (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-completed-hooks') + const completed = queueLine(records, 'queue-operation-enqueue') + + it('settles on its end line before the report turn opens, as the launch turn, with no second completion', async () => { + const server = await startServer() + const transcript = transcriptFile() + const replay = replayer(server, records, new Map([['*', transcript]])) + await replay([0, 1, 2, 3]) + expect(watch(server)?.reasons).toContain('recorded-task') + await replay([4, 5, 6, 7]) + // The launch turn's Stop lists it as `workflow`, which types it the same as the launch did. + expect(hookAt(records, 7).payload.background_tasks).toEqual([ + expect.objectContaining({ id: 'w1kuktaid', type: 'workflow', status: 'running' }) + ]) + expect(recordedTasks(server)?.get('w1kuktaid')).toEqual({ + kind: 'workflow', + launchToolUseId: 'toolu_01S3s8SyG9VLFN3QhiERnGmU' + }) + const settled = row(server) + expect(settled).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) + expect(typeof settled.turnCompletedAt).toBe('number') + + await replay([8, 9, 10, 11]) + expect(row(server)).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) + + // The capture's write order, by Claude's own stamps: the end line 17:00:28.690Z, the report + // turn's UserPromptSubmit 41 ms later (the fixture `t` of a line is the rig's poll time). + writeCaptured(records, completed, transcript) + await vi.waitFor(() => expect(row(server).state).toBe('done'), { timeout: 3_000 }) + expect(row(server)).toMatchObject({ mainAgent: { state: 'done' } }) + expect(row(server).interrupted).toBeUndefined() + // Why: the settled row repeats the Stop's turn stamp, which is how a completion is paired. + expect(row(server).turnCompletedAt).toBe(settled.turnCompletedAt) + + await replay([12]) + expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) + await replay([13, 14]) + expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + expect(row(server).turnCompletedAt).not.toBe(settled.turnCompletedAt) + }) +}) + +describe('/clear while a background workflow runs (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-clear-hooks') + const completed = queueLine(records, 'queue-operation-enqueue') + + it('keeps the workflow across the session change and settles on its end line in the new transcript', async () => { + const server = await startServer() + const dir = temporaryDir() + const first = join(dir, 'first.jsonl') + const cleared = join(dir, 'cleared.jsonl') + writeFileSync(first, '') + const files = new Map([ + [String(hookAt(records, 0).payload.transcript_path), first], + [String(hookAt(records, 10).payload.transcript_path), cleared] + ]) + expect(files.size).toBe(2) + const replay = replayer(server, records, files) + await replay([0, 1, 2, 3, 4, 5, 6, 7, 8]) + const beforeClear = row(server) + expect(beforeClear).toMatchObject({ state: 'working', mainAgent: { state: 'done' } }) + expect(typeof beforeClear.turnCompletedAt).toBe('number') + + expect(hookAt(records, 10).payload).toMatchObject({ + hook_event_name: 'SessionStart', + source: 'clear' + }) + await replay([9, 10]) + expect(row(server)).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) + expect(row(server).turnCompletedAt).toBe(beforeClear.turnCompletedAt) + expect(recordedTasks(server)?.has('wvdcggdcr')).toBe(true) + // Hand-built: stands for the new file's first rows, which the fixture does not keep. + writeFileSync(cleared, '{"type":"mode"}\n') + + // The agent's later hooks carry the new session id and no prompt id. + expect(hookAt(records, 11).payload.session_id).toBe(hookAt(records, 10).payload.session_id) + expect(hookAt(records, 11).payload).not.toHaveProperty('prompt_id') + await replay([11, 12, 13]) + expect(row(server)).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) + + // The capture's write order, by Claude's own stamps: the end line 17:09:27.467Z, the report + // turn's UserPromptSubmit 42 ms later. + expect(completed.file).toBe( + String(hookAt(records, 10).payload.transcript_path).split('/').pop() + ) + appendFileSync(cleared, `${completed.lines[0]}\n`) + await vi.waitFor(() => expect(row(server).state).toBe('done'), { timeout: 3_000 }) + expect(row(server).turnCompletedAt).toBe(beforeClear.turnCompletedAt) + + await replay([14, 15, 16]) + expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + }) +}) + +// Temporary: a workflow stopped from /tasks writes no end line (r6-w3), unlike a shell. Flip this +// story to expect `done` at that line once a capture shows Claude writing a killed row for one. +describe('a background workflow stopped from /tasks (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-tasks-kill-hooks') + // Why: the capture kept no transcript line to anchor the clock; only order matters here. + const t0 = Date.parse('2026-09-30T17:03:48.000Z') + + it('stays live until Claude next lists its tasks: no hook reports its end, and no timer may', async () => { + const server = await startServer() + const transcript = transcriptFile() + const replay = replayer(server, records, new Map([['*', transcript]]), t0) + await replay([0, 1, 2, 3, 4, 5, 6, 7, 8]) + expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'done' } }) + + // The capture: after `x`, no hook, not even SubagentStop, and nothing in the main transcript + // until the next typed prompt 135 s later. The workflow's agent stays on the roster too. + const stopped = keyAt(records, 'W3-x-stop-workflow') + expect( + records.some( + (record) => record.kind !== 'key' && record.t > stopped && record.t < hookAt(records, 9).t + ) + ).toBe(false) + vi.setSystemTime(t0 + (hookAt(records, 9).t - 1) * 1000) + // Hand-built: a line no reason reads, so a caught-up offset proves a tick ran at that time. + appendFileSync(transcript, '{"type":"mode"}\n') + await watchCaughtUp(server, transcript) + expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'done' } }) + expect(recordedTasks(server)?.has('wc6rxzj9v')).toBe(true) + + await replay([9, 10]) + expect(hookAt(records, 10).payload.background_tasks).toEqual([]) + expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + expect(recordedTasks(server)).toBeUndefined() + expect(rosterSize(server)).toBe(0) + }) +}) + +describe('a background workflow whose script waits between two agents (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-script-wait-hooks') + const completed = queueLine(records, 'queue-operation-enqueue') + + it('monitors through the wait after a normal launch turn, then settles on its end line', async () => { + const server = await startServer() + const transcript = transcriptFile() + const replay = replayer(server, records, new Map([['*', transcript]])) + await replay([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + expect(row(server)).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) + + // The capture: 20.1 s from agent 1's SubagentStop to agent 2's SubagentStart, with no hook and + // no main-transcript line between them. + expect(hookAt(records, 12).t - hookAt(records, 11).t).toBeGreaterThan(20) + await replay([12, 13]) + expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'done' } }) + expect(row(server).workingMode).not.toBe('monitoring') + await replay([14, 15, 16]) + expect(row(server)).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) + + // By Claude's own stamps the end line (18:06:08.108Z) precedes the report turn's prompt hook + // by 40 ms; the fixture's `t` for the line is the rig's later poll time. + writeCaptured(records, completed, transcript) + await vi.waitFor(() => expect(row(server).state).toBe('done'), { timeout: 3_000 }) + await replay([17, 18]) + expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + }) +}) + +// Temporary, with the /tasks stop above: a pause kills the running agent with no SubagentStop and +// writes no end line, so the roster's agent holds the pane until the next inventory. +describe('a background workflow paused from /tasks (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-pause-hooks') + const t0 = Date.parse('2026-09-30T18:12:28.000Z') + + it('reads working until the next typed turn lists no task, then done', async () => { + const server = await startServer() + const transcript = transcriptFile() + const replay = replayer(server, records, new Map([['*', transcript]]), t0) + await replay([0, 1, 2, 3, 4, 5, 6, 7, 8]) + const paused = keyAt(records, 'W6B-p-pause') + expect( + records.some( + (record) => record.kind !== 'key' && record.t > paused && record.t < hookAt(records, 9).t + ) + ).toBe(false) + vi.setSystemTime(t0 + (hookAt(records, 9).t - 1) * 1000) + appendFileSync(transcript, '{"type":"mode"}\n') + await watchCaughtUp(server, transcript) + expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'done' } }) + expect(rosterSize(server)).toBe(1) + expect(recordedTasks(server)?.has('wmucfg2gk')).toBe(true) + + await replay([9, 10, 11]) + expect(hookAt(records, 11).payload.background_tasks).toEqual([]) + expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + expect(recordedTasks(server)).toBeUndefined() + expect(rosterSize(server)).toBe(0) + }) +}) + +describe('an idle-prompt Ctrl+C while a background workflow runs (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-idle-ctrl-c-hooks') + const cancel = cancelLabelled(records, 'W5A-ctrl-c-1') + const completed = queueLine(records, 'queue-operation-enqueue') + + it('changes nothing: the workflow runs on and settles on its end line', async () => { + const server = await startServer() + const transcript = transcriptFile() + const replay = replayer(server, records, new Map([['*', transcript]])) + await replay([0, 1, 2, 3, 4, 5, 6, 7, 8]) + const idle = row(server) + expect(idle).toMatchObject({ state: 'working', mainAgent: { state: 'done' } }) + + // The capture: Claude painted only its exit hint; no row, no hook, and the sleep ran on. + expect(cancel).toMatchObject({ + interrupted_painted: false, + exit_hint_painted: true, + hooks_before_next_typed_prompt: [] + }) + vi.setSystemTime(captureEpoch(records) + cancel.t * 1000) + expect(pressCtrlC(server)).toBe(false) + expect(row(server)).toEqual(idle) + + await replay([9, 10, 11]) + expect(row(server)).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) + writeCaptured(records, completed, transcript) + await vi.waitFor(() => expect(storedTaskFact(server)).toBe(false), { timeout: 3_000 }) + expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + expect(row(server).turnCompletedAt).toBe(idle.turnCompletedAt) + }) +}) diff --git a/src/main/agent-hooks/server-relayed-claude-background-shell.test.ts b/src/main/agent-hooks/server-relayed-claude-background-shell.test.ts index f8e7adf7462e..8737c27898d7 100644 --- a/src/main/agent-hooks/server-relayed-claude-background-shell.test.ts +++ b/src/main/agent-hooks/server-relayed-claude-background-shell.test.ts @@ -1,7 +1,7 @@ -// The captured background-shell stories on a relayed pane (WSL's local relay: the desktop never -// infers a Ctrl+C on an SSH pane, whose input is not delivery-confirmed): hooks go through a real -// relay-side listener, which owns the task record and reads the transcript, and reach the desktop -// only as relayed rows. The relay never learns the Ctrl+C the desktop infers, so every row it +// The captured background-shell and -workflow stories on a relayed pane (WSL's local relay: the +// desktop never infers a Ctrl+C on an SSH pane, whose input is not delivery-confirmed): hooks go +// through a real relay-side listener, which owns the task record and reads the transcript, and +// reach the desktop only as relayed rows. The relay never learns the Ctrl+C the desktop infers, so every row it // publishes afterwards still names its own working main agent. import { appendFileSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' @@ -165,3 +165,53 @@ describe('a relayed Ctrl+C in the turn that launched a background shell (capture }) }) }) + +describe('a relayed Ctrl+C in the turn that launched a background workflow (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-ctrl-c-hooks') + const cancel = cancelLabelled(records, 'W2-ctrl-c-mid-essay') + const scan = records.find( + (record): record is CapturedTranscriptScan => + record.kind === 'transcript' && record.label === 'queue-operation-enqueue' + ) + if (!scan) { + throw new Error('the capture has no enqueue line') + } + // JSON.parse returns any; the timestamp is checked by Date.parse. + const parsed: Record = JSON.parse(scan.lines[0]) + const t0 = Date.parse(String(parsed.timestamp)) - scan.t * 1000 + + async function post(pane: SshPane, transcript: string, indices: number[]): Promise { + for (const index of indices) { + vi.setSystemTime(t0 + hookAt(records, index).t * 1000) + await pane.post({ ...hookAt(records, index).payload, transcript_path: transcript }) + } + } + + it('monitors the workflow after its agent stops, then settles on the end line the relay reads', async () => { + const pane = await startSshPane() + const transcript = join(temporaryDir('orca-relayed-workflow-transcript-'), 'session.jsonl') + writeFileSync(transcript, '') + await post(pane, transcript, [0, 1, 2, 3, 4, 5, 6, 7, 8]) + vi.setSystemTime(t0 + cancel.t * 1000) + expect(pressCtrlC(pane.desktop)).toBe(true) + + // 32 s later, long after the latch window: the agent's own hooks, relayed. + await post(pane, transcript, [9, 10, 11]) + expect(row(pane.desktop)).toMatchObject({ + state: 'working', + workingMode: 'monitoring', + mainAgent: { state: 'done', outcome: 'cancellation' } + }) + + vi.setSystemTime(t0 + scan.t * 1000) + appendFileSync(transcript, `${scan.lines[0]}\n`) + await vi.waitFor(() => expect(row(pane.desktop).workingMode).toBeUndefined(), { + timeout: 3_000 + }) + expect(row(pane.desktop)).toMatchObject({ + state: 'done', + interrupted: true, + mainAgent: { state: 'done', outcome: 'cancellation' } + }) + }) +}) diff --git a/src/main/agent-hooks/server/server-status-update.ts b/src/main/agent-hooks/server/server-status-update.ts index 5033a5ce667e..93a4e47896df 100644 --- a/src/main/agent-hooks/server/server-status-update.ts +++ b/src/main/agent-hooks/server/server-status-update.ts @@ -161,7 +161,7 @@ export abstract class AgentHookServerStatusUpdate extends AgentHookServerStatusA restatesRecords === true ? 'restates-records' : transcriptFact === true ? 'fact' : undefined ) if (latch.hold) { - // Why: the latch holds the row, not the evidence; a shell launched by a hook in flight at the + // Why: the latch holds the row, not the evidence; a task launched by a hook in flight at the // Ctrl+C still runs, and the store accepted the event for this pane. onAccepted?.() if ( diff --git a/src/shared/__fixtures__/claude-background-workflow-clear-hooks.jsonl b/src/shared/__fixtures__/claude-background-workflow-clear-hooks.jsonl new file mode 100644 index 000000000000..9f33c807d692 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-clear-hooks.jsonl @@ -0,0 +1,28 @@ +{"t": 0.0, "kind": "hook", "index": 0, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-opus-5-5[1m]"}} +{"t": 4.684, "kind": "key", "label": "enter-w4"} +{"t": 4.728, "kind": "hook", "index": 1, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000001", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 1: call the Workflow tool exactly once, passing this JavaScript verbatim as its script parameter (inline, do not write it to a file): export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 45.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r; After it is launched, reply with exactly the word LAUNCHED and stop. Do not wait for it."}} +{"t": 7.428, "kind": "hook", "index": 2, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 45.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_019fnVX4iPQFfCxfhWG4DvKW"}} +{"t": 7.525, "kind": "hook", "index": 3, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 45.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_response": {"status": "async_launched", "taskId": "wvdcggdcr", "taskType": "local_workflow", "workflowName": "capture-sleep", "runId": "wf_8e2d915f-0a1", "summary": "One agent runs a timed sleep", "transcriptDir": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/subagents/workflows/wf_8e2d915f-0a1", "scriptPath": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/workflows/scripts/capture-sleep-wf_8e2d915f-0a1.js"}, "tool_use_id": "toolu_019fnVX4iPQFfCxfhWG4DvKW", "duration_ms": 2}} +{"t": 7.536, "kind": "hook", "index": 4, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000001", "agent_id": "ab900000000000001", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 7.629, "kind": "hook", "index": 5, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 45.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_019fnVX4iPQFfCxfhWG4DvKW", "tool_response": "Workflow launched in background. Task ID: wvdcggdcr\nSummary: One agent runs a timed sleep\nTranscript dir: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/subagents/workflows/wf_8e2d915f-0a1\nScript file: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/workflows/scripts/capture-sleep-wf_8e2d915f-0a1.js\n(Edit this file with Write/Edit and re-invoke Workflow with {scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/workflows/scripts/capture-sleep-wf_8e2d915f-0a1.js\"} to iterate without resending the script.)\nRun ID: wf_8e2d915f-0a1\nTo resume after editing the script: Workflow({scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/workflows/scripts/capture-sleep-wf_8e2d915f-0a1.js\", resumeFromRunId: \"wf_8e2d915f-0a1\"}) — completed agents return cached results (cached results may themselves be empty — inspect journal.jsonl before assuming there is something to recover).\n\nYou will be notified when it completes. Use /workflows to watch live progress."}]}} +{"t": 8.889, "kind": "hook", "index": 6, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000001", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b9000002", "message_id": "00000000-0000-4000-8000-0000b9000003", "index": 0, "final": true, "delta": "LAUNCHED"}} +{"t": 8.889, "kind": "hook", "index": 7, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "LAUNCHED", "background_tasks": [{"id": "wvdcggdcr", "type": "workflow", "status": "running", "description": "One agent runs a timed sleep", "name": "capture-sleep"}], "session_crons": []}} +{"t": 10.17, "kind": "hook", "index": 8, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000001", "permission_mode": "bypassPermissions", "agent_id": "ab900000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 45.07", "description": "Sleep for 45.07 seconds"}, "tool_use_id": "toolu_01F2ja1S162dWvmgychTaff5"}} +{"t": 13.107, "kind": "key", "label": "enter-/clear"} +{"t": 13.143, "kind": "hook", "index": 9, "sleep_procs": ["65940 65938 SN 00:02 sleep 45.07"], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000004", "hook_event_name": "SessionEnd", "reason": "clear"}} +{"t": 13.247, "kind": "hook", "index": 10, "sleep_procs": ["65940 65938 SN 00:03 sleep 45.07"], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "clear"}} +{"t": 55.375, "kind": "hook", "index": 11, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "permission_mode": "bypassPermissions", "agent_id": "ab900000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 45.07", "description": "Sleep for 45.07 seconds"}, "tool_response": {"stdout": "", "stderr": "", "interrupted": false, "isImage": false, "noOutputExpected": false}, "tool_use_id": "toolu_01F2ja1S162dWvmgychTaff5", "duration_ms": 45106}} +{"t": 55.469, "kind": "hook", "index": 12, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "permission_mode": "bypassPermissions", "agent_id": "ab900000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Bash", "tool_input": {"command": "sleep 45.07", "description": "Sleep for 45.07 seconds"}, "tool_use_id": "toolu_01F2ja1S162dWvmgychTaff5", "tool_response": "(Bash completed with no output)"}]}} +{"t": 56.641, "kind": "hook", "index": 13, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "permission_mode": "bypassPermissions", "agent_id": "ab900000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "SubagentStop", "stop_hook_active": false, "agent_transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005/subagents/workflows/wf_8e2d915f-0a1/agent-ab900000000000001.jsonl", "last_assistant_message": "SLEPT", "background_tasks": [{"id": "wvdcggdcr", "type": "workflow", "status": "running", "description": "One agent runs a timed sleep", "name": "capture-sleep"}], "session_crons": []}} +{"t": 56.75, "kind": "transcript", "label": "queue-operation-enqueue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"enqueue\",\"timestamp\":\"2026-09-30T17:09:27.467Z\",\"sessionId\":\"00000000-0000-4000-8000-0000b9000005\",\"content\":\"\\nwvdcggdcr\\ntoolu_019fnVX4iPQFfCxfhWG4DvKW\\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/tasks/wvdcggdcr.output\\ncompleted\\nDynamic workflow \\\"One agent runs a timed sleep\\\" completed\\n\\\"SLEPT\\\"\\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/subagents/workflows/wf_8e2d915f-0a1/journal.jsonl — one {\\\"type\\\":\\\"result\\\",...} line per completed agent with its full return value.\\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/workflows/scripts/capture-sleep-wf_8e2d915f-0a1.js', resumeFromRunId: 'wf_8e2d915f-0a1'}) — agents whose (prompt, opts) are unchanged replay from cache.\\n1100011734149222\\n\"}"], "file": "00000000-0000-4000-8000-0000b9000005.jsonl"} +{"t": 56.759, "kind": "hook", "index": 14, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000006", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "\nwvdcggdcr\ntoolu_019fnVX4iPQFfCxfhWG4DvKW\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/tasks/wvdcggdcr.output\ncompleted\nDynamic workflow \"One agent runs a timed sleep\" completed\n\"SLEPT\"\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/subagents/workflows/wf_8e2d915f-0a1/journal.jsonl — one {\"type\":\"result\",...} line per completed agent with its full return value.\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000000/workflows/scripts/capture-sleep-wf_8e2d915f-0a1.js', resumeFromRunId: 'wf_8e2d915f-0a1'}) — agents whose (prompt, opts) are unchanged replay from cache.\n1100011734149222\n"}} +{"t": 56.821, "kind": "transcript", "label": "queue-operation-dequeue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"dequeue\",\"timestamp\":\"2026-09-30T17:09:27.470Z\",\"sessionId\":\"00000000-0000-4000-8000-0000b9000005\"}"], "file": "00000000-0000-4000-8000-0000b9000005.jsonl"} +{"t": 58.421, "kind": "hook", "index": 15, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000006", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b9000007", "message_id": "00000000-0000-4000-8000-0000b9000008", "index": 0, "final": false, "delta": "A background workflow from before the `/clear` has finished. \"One agent runs a timed sleep\" ran its single agent, which returned `\"SLEPT\"` after about 49 seconds with no errors.\n\n"}} +{"t": 59.337, "kind": "hook", "index": 16, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000006", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "A background workflow from before the `/clear` has finished. \"One agent runs a timed sleep\" ran its single agent, which returned `\"SLEPT\"` after about 49 seconds with no errors.\n\nThere's no pending request since the clear. What would you like to do next?", "background_tasks": [], "session_crons": []}} +{"t": 59.337, "kind": "hook", "index": 17, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000006", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b9000007", "message_id": "00000000-0000-4000-8000-0000b9000008", "index": 1, "final": true, "delta": "There's no pending request since the clear. What would you like to do next?"}} +{"t": 63.508, "kind": "key", "label": "enter-ok2"} +{"t": 63.544, "kind": "hook", "index": 18, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000009", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 2: reply with exactly the word OK. Do not use any tools."}} +{"t": 65.12, "kind": "hook", "index": 19, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000009", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b900000a", "message_id": "00000000-0000-4000-8000-0000b900000b", "index": 0, "final": true, "delta": "OK"}} +{"t": 65.12, "kind": "hook", "index": 20, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b9000009", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "OK", "background_tasks": [], "session_crons": []}} +{"t": 69.499, "kind": "key", "label": "enter-exit"} +{"t": 69.819, "kind": "hook", "index": 21, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b9000005", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b9000005.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b900000c", "hook_event_name": "SessionEnd", "reason": "prompt_input_exit"}} diff --git a/src/shared/__fixtures__/claude-background-workflow-clear-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-clear-hooks.meta.json new file mode 100644 index 000000000000..c23f18fe7263 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-clear-hooks.meta.json @@ -0,0 +1,15 @@ +{ + "source_scenario": "r6-w4-run1", + "claude_version": "2.1.285 (Claude Code)", + "platform": "darwin", + "cols": 120, + "rows": 40, + "recorded_at": "2026-09-30", + "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", + "note": "Claude launches a one-agent background workflow (`sleep 45.07`) and the turn ends (its Stop lists it). /clear: SessionEnd (clear), SessionStart (clear) under a new session id. The agent's later hooks carry the NEW session id and no prompt id. The end row (completed, the launch's ids) lands in the NEW session's transcript, whose `` and diagnostics still name the old session. Write order by Claude's own row timestamp: end row 17:09:27.467Z; the report turn's UserPromptSubmit hook is 42 ms later. `file` names which transcript a line is in.", + "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (b9); each workflow agent id by a same-shape placeholder ab9…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped).", + "files": [ + "00000000-0000-4000-8000-0000b9000005.jsonl", + "00000000-0000-4000-8000-0000b9000000.jsonl" + ] +} diff --git a/src/shared/__fixtures__/claude-background-workflow-completed-hooks.jsonl b/src/shared/__fixtures__/claude-background-workflow-completed-hooks.jsonl new file mode 100644 index 000000000000..d895674521ff --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-completed-hooks.jsonl @@ -0,0 +1,24 @@ +{"t": 0.0, "kind": "hook", "index": 0, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-opus-5-5[1m]"}} +{"t": 4.716, "kind": "key", "label": "enter-w1"} +{"t": 4.767, "kind": "hook", "index": 1, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 1: call the Workflow tool exactly once, passing this JavaScript verbatim as its script parameter (inline, do not write it to a file): export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r; After it is launched, reply with exactly the word LAUNCHED and stop. Do not wait for it."}} +{"t": 7.696, "kind": "hook", "index": 2, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_01S3s8SyG9VLFN3QhiERnGmU"}} +{"t": 7.804, "kind": "hook", "index": 3, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_response": {"status": "async_launched", "taskId": "w1kuktaid", "taskType": "local_workflow", "workflowName": "capture-sleep", "runId": "wf_ca44ff70-bf9", "summary": "One agent runs a timed sleep", "transcriptDir": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/subagents/workflows/wf_ca44ff70-bf9", "scriptPath": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/workflows/scripts/capture-sleep-wf_ca44ff70-bf9.js"}, "tool_use_id": "toolu_01S3s8SyG9VLFN3QhiERnGmU", "duration_ms": 2}} +{"t": 7.816, "kind": "hook", "index": 4, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "agent_id": "ab600000000000001", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 7.911, "kind": "hook", "index": 5, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_01S3s8SyG9VLFN3QhiERnGmU", "tool_response": "Workflow launched in background. Task ID: w1kuktaid\nSummary: One agent runs a timed sleep\nTranscript dir: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/subagents/workflows/wf_ca44ff70-bf9\nScript file: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/workflows/scripts/capture-sleep-wf_ca44ff70-bf9.js\n(Edit this file with Write/Edit and re-invoke Workflow with {scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/workflows/scripts/capture-sleep-wf_ca44ff70-bf9.js\"} to iterate without resending the script.)\nRun ID: wf_ca44ff70-bf9\nTo resume after editing the script: Workflow({scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/workflows/scripts/capture-sleep-wf_ca44ff70-bf9.js\", resumeFromRunId: \"wf_ca44ff70-bf9\"}) — completed agents return cached results (cached results may themselves be empty — inspect journal.jsonl before assuming there is something to recover).\n\nYou will be notified when it completes. Use /workflows to watch live progress."}]}} +{"t": 9.246, "kind": "hook", "index": 6, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b6000002", "message_id": "00000000-0000-4000-8000-0000b6000003", "index": 0, "final": true, "delta": "LAUNCHED"}} +{"t": 9.247, "kind": "hook", "index": 7, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "LAUNCHED", "background_tasks": [{"id": "w1kuktaid", "type": "workflow", "status": "running", "description": "One agent runs a timed sleep", "name": "capture-sleep"}], "session_crons": []}} +{"t": 10.575, "kind": "hook", "index": 8, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "agent_id": "ab600000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for 40.07 seconds"}, "tool_use_id": "toolu_0185aq7B8Xp3ZQ7M3aCDHyQg"}} +{"t": 50.781, "kind": "hook", "index": 9, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "agent_id": "ab600000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for 40.07 seconds"}, "tool_response": {"stdout": "", "stderr": "", "interrupted": false, "isImage": false, "noOutputExpected": false}, "tool_use_id": "toolu_0185aq7B8Xp3ZQ7M3aCDHyQg", "duration_ms": 40105}} +{"t": 50.876, "kind": "hook", "index": 10, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "agent_id": "ab600000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for 40.07 seconds"}, "tool_use_id": "toolu_0185aq7B8Xp3ZQ7M3aCDHyQg", "tool_response": "(Bash completed with no output)"}]}} +{"t": 52.01, "kind": "hook", "index": 11, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000001", "permission_mode": "bypassPermissions", "agent_id": "ab600000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "SubagentStop", "stop_hook_active": false, "agent_transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/subagents/workflows/wf_ca44ff70-bf9/agent-ab600000000000001.jsonl", "last_assistant_message": "SLEPT", "background_tasks": [{"id": "w1kuktaid", "type": "workflow", "status": "running", "description": "One agent runs a timed sleep", "name": "capture-sleep"}], "session_crons": []}} +{"t": 52.124, "kind": "transcript", "label": "queue-operation-enqueue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"enqueue\",\"timestamp\":\"2026-09-30T17:00:28.690Z\",\"sessionId\":\"00000000-0000-4000-8000-0000b6000000\",\"content\":\"\\nw1kuktaid\\ntoolu_01S3s8SyG9VLFN3QhiERnGmU\\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/tasks/w1kuktaid.output\\ncompleted\\nDynamic workflow \\\"One agent runs a timed sleep\\\" completed\\n\\\"SLEPT\\\"\\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/subagents/workflows/wf_ca44ff70-bf9/journal.jsonl — one {\\\"type\\\":\\\"result\\\",...} line per completed agent with its full return value.\\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/workflows/scripts/capture-sleep-wf_ca44ff70-bf9.js', resumeFromRunId: 'wf_ca44ff70-bf9'}) — agents whose (prompt, opts) are unchanged replay from cache.\\n110009222144316\\n\"}"]} +{"t": 52.13, "kind": "hook", "index": 12, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000004", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "\nw1kuktaid\ntoolu_01S3s8SyG9VLFN3QhiERnGmU\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/tasks/w1kuktaid.output\ncompleted\nDynamic workflow \"One agent runs a timed sleep\" completed\n\"SLEPT\"\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/subagents/workflows/wf_ca44ff70-bf9/journal.jsonl — one {\"type\":\"result\",...} line per completed agent with its full return value.\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000/workflows/scripts/capture-sleep-wf_ca44ff70-bf9.js', resumeFromRunId: 'wf_ca44ff70-bf9'}) — agents whose (prompt, opts) are unchanged replay from cache.\n110009222144316\n"}} +{"t": 52.21, "kind": "transcript", "label": "queue-operation-dequeue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"dequeue\",\"timestamp\":\"2026-09-30T17:00:28.694Z\",\"sessionId\":\"00000000-0000-4000-8000-0000b6000000\"}"]} +{"t": 54.448, "kind": "hook", "index": 13, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000004", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b6000005", "message_id": "00000000-0000-4000-8000-0000b6000006", "index": 0, "final": true, "delta": "The capture-sleep workflow finished without errors. Its one agent ran the 40.07-second sleep and replied \"SLEPT\"; the run took about 44 seconds in total."}} +{"t": 54.453, "kind": "hook", "index": 14, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000004", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "The capture-sleep workflow finished without errors. Its one agent ran the 40.07-second sleep and replied \"SLEPT\"; the run took about 44 seconds in total.", "background_tasks": [], "session_crons": []}} +{"t": 64.233, "kind": "key", "label": "enter-ok2"} +{"t": 64.269, "kind": "hook", "index": 15, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000007", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 2: reply with exactly the word OK. Do not use any tools."}} +{"t": 66.141, "kind": "hook", "index": 17, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000007", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "OK", "background_tasks": [], "session_crons": []}} +{"t": 66.145, "kind": "hook", "index": 16, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b6000007", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b6000008", "message_id": "00000000-0000-4000-8000-0000b6000009", "index": 0, "final": true, "delta": "OK"}} +{"t": 70.49, "kind": "key", "label": "enter-exit"} +{"t": 70.817, "kind": "hook", "index": 18, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b6000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b6000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b600000a", "hook_event_name": "SessionEnd", "reason": "prompt_input_exit"}} diff --git a/src/shared/__fixtures__/claude-background-workflow-completed-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-completed-hooks.meta.json new file mode 100644 index 000000000000..6e8cb2b9235e --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-completed-hooks.meta.json @@ -0,0 +1,11 @@ +{ + "source_scenario": "r6-w1-run1", + "claude_version": "2.1.285 (Claude Code)", + "platform": "darwin", + "cols": 120, + "rows": 40, + "recorded_at": "2026-09-30", + "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", + "note": "Claude launches a one-agent background workflow (the agent runs `sleep 40.07`) and the turn ends; the launch turn's Stop lists it as `type: workflow`. The workflow completes: its agent's SubagentStop, then Claude's `queue-operation` enqueue row (completed, the launch's task id and tool-use id, plus `` and ``), then a report turn whose Stop lists nothing. Write order by Claude's own row timestamp: end row 17:00:28.690Z; the report turn's UserPromptSubmit hook is 41 ms later. The fixture `t` of a transcript line is the rig's later poll time (margin 6 ms), not its write time.", + "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (b6); each workflow agent id by a same-shape placeholder ab6…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped)." +} diff --git a/src/shared/__fixtures__/claude-background-workflow-ctrl-c-hooks.jsonl b/src/shared/__fixtures__/claude-background-workflow-ctrl-c-hooks.jsonl new file mode 100644 index 000000000000..cd848dcd3b2d --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-ctrl-c-hooks.jsonl @@ -0,0 +1,27 @@ +{"t": 0.0, "kind": "hook", "index": 0, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-opus-5-5[1m]"}} +{"t": 4.677, "kind": "key", "label": "enter-w2"} +{"t": 4.725, "kind": "hook", "index": 1, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 1: call the Workflow tool exactly once, passing this JavaScript verbatim as its script parameter (inline, do not write it to a file): export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r; Right after it is launched, without using any more tools and without waiting for it, write an 800-word essay about lighthouses."}} +{"t": 7.644, "kind": "hook", "index": 2, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_01KDk2uKk754Pvw5EpcecXru"}} +{"t": 7.757, "kind": "hook", "index": 3, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_response": {"status": "async_launched", "taskId": "wuxzs3s3c", "taskType": "local_workflow", "workflowName": "capture-sleep", "runId": "wf_3a9bc488-fce", "summary": "One agent runs a timed sleep", "transcriptDir": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/subagents/workflows/wf_3a9bc488-fce", "scriptPath": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/workflows/scripts/capture-sleep-wf_3a9bc488-fce.js"}, "tool_use_id": "toolu_01KDk2uKk754Pvw5EpcecXru", "duration_ms": 2}} +{"t": 7.761, "kind": "hook", "index": 4, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "agent_id": "ab700000000000001", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 7.874, "kind": "hook", "index": 5, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_01KDk2uKk754Pvw5EpcecXru", "tool_response": "Workflow launched in background. Task ID: wuxzs3s3c\nSummary: One agent runs a timed sleep\nTranscript dir: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/subagents/workflows/wf_3a9bc488-fce\nScript file: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/workflows/scripts/capture-sleep-wf_3a9bc488-fce.js\n(Edit this file with Write/Edit and re-invoke Workflow with {scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/workflows/scripts/capture-sleep-wf_3a9bc488-fce.js\"} to iterate without resending the script.)\nRun ID: wf_3a9bc488-fce\nTo resume after editing the script: Workflow({scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/workflows/scripts/capture-sleep-wf_3a9bc488-fce.js\", resumeFromRunId: \"wf_3a9bc488-fce\"}) — completed agents return cached results (cached results may themselves be empty — inspect journal.jsonl before assuming there is something to recover).\n\nYou will be notified when it completes. Use /workflows to watch live progress."}]}} +{"t": 10.516, "kind": "hook", "index": 6, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "permission_mode": "bypassPermissions", "agent_id": "ab700000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for 40.07 seconds"}, "tool_use_id": "toolu_01GuviNjJTcEYNbKZroJmkre"}} +{"t": 17.848, "kind": "hook", "index": 7, "sleep_procs": ["47360 47358 SN 00:07 sleep 40.07"], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b7000002", "message_id": "00000000-0000-4000-8000-0000b7000003", "index": 0, "final": false, "delta": "The workflow is running in the background (task `wuxzs3s3c`). Here's the essay while it runs:\n\n---\n\n"}} +{"t": 17.942, "kind": "hook", "index": 8, "sleep_procs": ["47360 47358 SN 00:08 sleep 40.07"], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b7000002", "message_id": "00000000-0000-4000-8000-0000b7000003", "index": 1, "final": false, "delta": "## Lighthouses: The Keepers of the Edge\n\nFew buildings carry as much meaning as the lighthouse. It stands where land ends and open water begins, and for centuries it has meant both warning and welcome. It tells sailors to keep off the rocks, and it also tells them they are nearly home. Most modern lighthouses are automated, and satellite navigation has taken over much of their job. Even so, people still feel strongly about them, and the reason is worth looking at.\n\nThe oldest famous lighthouse was the Pharos of Alexandria. It was built in the third century BCE on a small island off the Egyptian coast. Ancient writers say it rose more than a hundred meters, which would have made it one of the tallest structures in the ancient world. A fire burned at the top, and some accounts say bronze mirrors reflected its light far out to sea. The Pharos stood for more than a millennium before earthquakes brought it down. Its name lasted longer than the building. In French, Spanish, Italian, and several other languages, the word for lighthouse still comes from \"Pharos.\"\n\nFor most of history, lighthouse technology changed slowly. Early lights burned wood, coal, or candles, and they were dim, smoky, and unreliable. Oil lamps were a real improvement, especially the Argand lamp of the late eighteenth century. Its hollow circular wick drew air through the center of the flame, which made the flame brighter and cleaner. The biggest breakthrough came in 1822, when the French physicist Augustin-Jean Fresnel introduced his lens. Fresnel's design used concentric rings of glass prisms. It captured light that would otherwise scatter and bent it into a single powerful horizontal beam. A Fresnel lens could send a lamp's glow twenty miles or more across the water. Its tiered glass also became the familiar image of a lighthouse lens. Fresnel lenses were graded by \"order,\" from the huge first-order lenses on major seacoasts to small sixth-order lenses in harbors and on rivers.\n\n"}} +{"t": 18.134, "kind": "cancel", "label": "W2-ctrl-c-mid-essay", "interrupted_painted": true, "exit_hint_painted": false, "hooks_before_next_typed_prompt": []} +{"t": 18.282, "kind": "transcript", "label": "interrupt-notice", "lines": ["{\"parentUuid\":\"00000000-0000-4000-8000-0000b7000004\",\"isSidechain\":false,\"promptId\":\"00000000-0000-4000-8000-0000b7000001\",\"type\":\"user\",\"message\":{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"[Request interrupted by user]\"}]},\"uuid\":\"00000000-0000-4000-8000-0000b7000005\",\"timestamp\":\"2026-09-30T17:01:48.164Z\",\"interruptedMessageId\":\"msg_0000000000000000000000b7\",\"session_id\":\"00000000-0000-4000-8000-0000b7000000\",\"userType\":\"external\",\"entrypoint\":\"cli\",\"cwd\":\"/Users/uuuuu/orca-qa/r1r2-capture/work\",\"sessionId\":\"00000000-0000-4000-8000-0000b7000000\",\"version\":\"2.1.285\",\"gitBranch\":\"HEAD\"}"]} +{"t": 50.725, "kind": "hook", "index": 9, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "permission_mode": "bypassPermissions", "agent_id": "ab700000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for 40.07 seconds"}, "tool_response": {"stdout": "", "stderr": "", "interrupted": false, "isImage": false, "noOutputExpected": false}, "tool_use_id": "toolu_01GuviNjJTcEYNbKZroJmkre", "duration_ms": 40102}} +{"t": 50.821, "kind": "hook", "index": 10, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "permission_mode": "bypassPermissions", "agent_id": "ab700000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for 40.07 seconds"}, "tool_use_id": "toolu_01GuviNjJTcEYNbKZroJmkre", "tool_response": "(Bash completed with no output)"}]}} +{"t": 51.984, "kind": "hook", "index": 11, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000001", "permission_mode": "bypassPermissions", "agent_id": "ab700000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "SubagentStop", "stop_hook_active": false, "agent_transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/subagents/workflows/wf_3a9bc488-fce/agent-ab700000000000001.jsonl", "last_assistant_message": "SLEPT", "background_tasks": [{"id": "wuxzs3s3c", "type": "workflow", "status": "running", "description": "One agent runs a timed sleep", "name": "capture-sleep"}], "session_crons": []}} +{"t": 52.095, "kind": "transcript", "label": "queue-operation-enqueue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"enqueue\",\"timestamp\":\"2026-09-30T17:02:22.086Z\",\"sessionId\":\"00000000-0000-4000-8000-0000b7000000\",\"content\":\"\\nwuxzs3s3c\\ntoolu_01KDk2uKk754Pvw5EpcecXru\\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/tasks/wuxzs3s3c.output\\ncompleted\\nDynamic workflow \\\"One agent runs a timed sleep\\\" completed\\n\\\"SLEPT\\\"\\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/subagents/workflows/wf_3a9bc488-fce/journal.jsonl — one {\\\"type\\\":\\\"result\\\",...} line per completed agent with its full return value.\\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/workflows/scripts/capture-sleep-wf_3a9bc488-fce.js', resumeFromRunId: 'wf_3a9bc488-fce'}) — agents whose (prompt, opts) are unchanged replay from cache.\\n110009226144344\\n\"}"]} +{"t": 52.099, "kind": "hook", "index": 12, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000006", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "\nwuxzs3s3c\ntoolu_01KDk2uKk754Pvw5EpcecXru\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/tasks/wuxzs3s3c.output\ncompleted\nDynamic workflow \"One agent runs a timed sleep\" completed\n\"SLEPT\"\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/subagents/workflows/wf_3a9bc488-fce/journal.jsonl — one {\"type\":\"result\",...} line per completed agent with its full return value.\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000/workflows/scripts/capture-sleep-wf_3a9bc488-fce.js', resumeFromRunId: 'wf_3a9bc488-fce'}) — agents whose (prompt, opts) are unchanged replay from cache.\n110009226144344\n"}} +{"t": 52.179, "kind": "transcript", "label": "queue-operation-dequeue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"dequeue\",\"timestamp\":\"2026-09-30T17:02:22.089Z\",\"sessionId\":\"00000000-0000-4000-8000-0000b7000000\"}"]} +{"t": 55.095, "kind": "hook", "index": 13, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000006", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b7000007", "message_id": "00000000-0000-4000-8000-0000b7000008", "index": 0, "final": false, "delta": "The `capture-sleep` workflow finished. The single `sleeper` agent ran the 40.07-second sleep and replied `SLEPT`. The whole run took about 44 seconds with no errors.\n\n"}} +{"t": 55.109, "kind": "hook", "index": 14, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000006", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "The `capture-sleep` workflow finished. The single `sleeper` agent ran the 40.07-second sleep and replied `SLEPT`. The whole run took about 44 seconds with no errors.\n\nYou interrupted the lighthouse essay partway through, in the section on Fresnel lenses. Should I finish it?", "background_tasks": [], "session_crons": []}} +{"t": 55.109, "kind": "hook", "index": 15, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000006", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b7000007", "message_id": "00000000-0000-4000-8000-0000b7000008", "index": 1, "final": true, "delta": "You interrupted the lighthouse essay partway through, in the section on Fresnel lenses. Should I finish it?"}} +{"t": 60.756, "kind": "key", "label": "enter-ok2"} +{"t": 60.802, "kind": "hook", "index": 16, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000009", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 2: reply with exactly the word OK. Do not use any tools."}} +{"t": 62.664, "kind": "hook", "index": 17, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000009", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b700000a", "message_id": "00000000-0000-4000-8000-0000b700000b", "index": 0, "final": true, "delta": "OK"}} +{"t": 62.665, "kind": "hook", "index": 18, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b7000009", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "OK", "background_tasks": [], "session_crons": []}} +{"t": 66.978, "kind": "key", "label": "enter-exit"} +{"t": 67.31, "kind": "hook", "index": 19, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b7000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b7000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b700000c", "hook_event_name": "SessionEnd", "reason": "prompt_input_exit"}} diff --git a/src/shared/__fixtures__/claude-background-workflow-ctrl-c-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-ctrl-c-hooks.meta.json new file mode 100644 index 000000000000..3887e1f46460 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-ctrl-c-hooks.meta.json @@ -0,0 +1,11 @@ +{ + "source_scenario": "r6-w2-run1", + "claude_version": "2.1.285 (Claude Code)", + "platform": "darwin", + "cols": 120, + "rows": 40, + "recorded_at": "2026-09-30", + "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", + "note": "Claude launches a one-agent background workflow (`sleep 40.07`) and writes a long reply; Ctrl+C mid-reply, in the same turn. Claude writes its interrupt notice row (kept, as a line that is not an end row), sends no hook and no Stop, and keeps the workflow running. 32.6 s later its agent's PostToolUse and SubagentStop arrive under the cancelled turn's prompt id; the end row (completed) follows 75 ms after SubagentStop by its own timestamp, then the report turn.", + "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (b7); each workflow agent id by a same-shape placeholder ab7…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped)." +} diff --git a/src/shared/__fixtures__/claude-background-workflow-idle-ctrl-c-hooks.jsonl b/src/shared/__fixtures__/claude-background-workflow-idle-ctrl-c-hooks.jsonl new file mode 100644 index 000000000000..602f28c16d17 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-idle-ctrl-c-hooks.jsonl @@ -0,0 +1,25 @@ +{"t": 0.0, "kind": "hook", "index": 0, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-opus-5-5[1m]"}} +{"t": 4.726, "kind": "key", "label": "enter-w5a"} +{"t": 4.774, "kind": "hook", "index": 1, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 1: call the Workflow tool exactly once, passing this JavaScript verbatim as its script parameter (inline, do not write it to a file): export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r; After it is launched, reply with exactly the word LAUNCHED and stop. Do not wait for it."}} +{"t": 8.025, "kind": "hook", "index": 2, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_01RNsu2N2FUFk8ufUEm67hD6"}} +{"t": 8.116, "kind": "hook", "index": 3, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_response": {"status": "async_launched", "taskId": "w1z6joqs6", "taskType": "local_workflow", "workflowName": "capture-sleep", "runId": "wf_b17ad5b8-0ce", "summary": "One agent runs a timed sleep", "transcriptDir": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/subagents/workflows/wf_b17ad5b8-0ce", "scriptPath": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/workflows/scripts/capture-sleep-wf_b17ad5b8-0ce.js"}, "tool_use_id": "toolu_01RNsu2N2FUFk8ufUEm67hD6", "duration_ms": 2}} +{"t": 8.13, "kind": "hook", "index": 4, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "agent_id": "aba00000000000001", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 8.216, "kind": "hook", "index": 5, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 40.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_01RNsu2N2FUFk8ufUEm67hD6", "tool_response": "Workflow launched in background. Task ID: w1z6joqs6\nSummary: One agent runs a timed sleep\nTranscript dir: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/subagents/workflows/wf_b17ad5b8-0ce\nScript file: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/workflows/scripts/capture-sleep-wf_b17ad5b8-0ce.js\n(Edit this file with Write/Edit and re-invoke Workflow with {scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/workflows/scripts/capture-sleep-wf_b17ad5b8-0ce.js\"} to iterate without resending the script.)\nRun ID: wf_b17ad5b8-0ce\nTo resume after editing the script: Workflow({scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/workflows/scripts/capture-sleep-wf_b17ad5b8-0ce.js\", resumeFromRunId: \"wf_b17ad5b8-0ce\"}) — completed agents return cached results (cached results may themselves be empty — inspect journal.jsonl before assuming there is something to recover).\n\nYou will be notified when it completes. Use /workflows to watch live progress."}]}} +{"t": 9.484, "kind": "hook", "index": 6, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000ba000002", "message_id": "00000000-0000-4000-8000-0000ba000003", "index": 0, "final": true, "delta": "LAUNCHED"}} +{"t": 9.484, "kind": "hook", "index": 7, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "LAUNCHED", "background_tasks": [{"id": "w1z6joqs6", "type": "workflow", "status": "running", "description": "One agent runs a timed sleep", "name": "capture-sleep"}], "session_crons": []}} +{"t": 10.098, "kind": "hook", "index": 8, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "agent_id": "aba00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for about 40 seconds"}, "tool_use_id": "toolu_01RXdCJRZ8QxzBJvtwhZviXq"}} +{"t": 12.688, "kind": "cancel", "label": "W5A-ctrl-c-1", "interrupted_painted": false, "exit_hint_painted": true, "hooks_before_next_typed_prompt": []} +{"t": 50.286, "kind": "hook", "index": 9, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "agent_id": "aba00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for about 40 seconds"}, "tool_response": {"stdout": "", "stderr": "", "interrupted": false, "isImage": false, "noOutputExpected": false}, "tool_use_id": "toolu_01RXdCJRZ8QxzBJvtwhZviXq", "duration_ms": 40103}} +{"t": 50.367, "kind": "hook", "index": 10, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "agent_id": "aba00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Bash", "tool_input": {"command": "sleep 40.07", "description": "Sleep for about 40 seconds"}, "tool_use_id": "toolu_01RXdCJRZ8QxzBJvtwhZviXq", "tool_response": "(Bash completed with no output)"}]}} +{"t": 51.366, "kind": "hook", "index": 11, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000001", "permission_mode": "bypassPermissions", "agent_id": "aba00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "SubagentStop", "stop_hook_active": false, "agent_transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/subagents/workflows/wf_b17ad5b8-0ce/agent-aba00000000000001.jsonl", "last_assistant_message": "SLEPT", "background_tasks": [{"id": "w1z6joqs6", "type": "workflow", "status": "running", "description": "One agent runs a timed sleep", "name": "capture-sleep"}], "session_crons": []}} +{"t": 51.449, "kind": "transcript", "label": "queue-operation-enqueue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"enqueue\",\"timestamp\":\"2026-09-30T17:58:10.335Z\",\"sessionId\":\"00000000-0000-4000-8000-0000ba000000\",\"content\":\"\\nw1z6joqs6\\ntoolu_01RNsu2N2FUFk8ufUEm67hD6\\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/tasks/w1z6joqs6.output\\ncompleted\\nDynamic workflow \\\"One agent runs a timed sleep\\\" completed\\n\\\"SLEPT\\\"\\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/subagents/workflows/wf_b17ad5b8-0ce/journal.jsonl — one {\\\"type\\\":\\\"result\\\",...} line per completed agent with its full return value.\\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/workflows/scripts/capture-sleep-wf_b17ad5b8-0ce.js', resumeFromRunId: 'wf_b17ad5b8-0ce'}) — agents whose (prompt, opts) are unchanged replay from cache.\\n110009221143348\\n\"}"]} +{"t": 51.449, "kind": "transcript", "label": "queue-operation-dequeue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"dequeue\",\"timestamp\":\"2026-09-30T17:58:10.338Z\",\"sessionId\":\"00000000-0000-4000-8000-0000ba000000\"}"]} +{"t": 51.47, "kind": "hook", "index": 12, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000004", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "\nw1z6joqs6\ntoolu_01RNsu2N2FUFk8ufUEm67hD6\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/tasks/w1z6joqs6.output\ncompleted\nDynamic workflow \"One agent runs a timed sleep\" completed\n\"SLEPT\"\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/subagents/workflows/wf_b17ad5b8-0ce/journal.jsonl — one {\"type\":\"result\",...} line per completed agent with its full return value.\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000/workflows/scripts/capture-sleep-wf_b17ad5b8-0ce.js', resumeFromRunId: 'wf_b17ad5b8-0ce'}) — agents whose (prompt, opts) are unchanged replay from cache.\n110009221143348\n"}} +{"t": 53.87, "kind": "hook", "index": 13, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000004", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000ba000005", "message_id": "00000000-0000-4000-8000-0000ba000006", "index": 0, "final": true, "delta": "The capture-sleep workflow finished without errors. Its one agent, `sleeper`, ran the 40.07-second sleep and replied `SLEPT`. The whole run took about 43 seconds."}} +{"t": 53.878, "kind": "hook", "index": 14, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000004", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "The capture-sleep workflow finished without errors. Its one agent, `sleeper`, ran the 40.07-second sleep and replied `SLEPT`. The whole run took about 43 seconds.", "background_tasks": [], "session_crons": []}} +{"t": 60.748, "kind": "key", "label": "enter-ok2"} +{"t": 60.786, "kind": "hook", "index": 15, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000007", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 2: reply with exactly the word OK. Do not use any tools."}} +{"t": 63.335, "kind": "hook", "index": 16, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000007", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "OK", "background_tasks": [], "session_crons": []}} +{"t": 63.335, "kind": "hook", "index": 17, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba000007", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000ba000008", "message_id": "00000000-0000-4000-8000-0000ba000009", "index": 0, "final": true, "delta": "OK"}} +{"t": 67.695, "kind": "key", "label": "enter-exit"} +{"t": 68.03, "kind": "hook", "index": 18, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000ba000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000ba000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000ba00000a", "hook_event_name": "SessionEnd", "reason": "prompt_input_exit"}} diff --git a/src/shared/__fixtures__/claude-background-workflow-idle-ctrl-c-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-idle-ctrl-c-hooks.meta.json new file mode 100644 index 000000000000..c68faf0c835e --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-idle-ctrl-c-hooks.meta.json @@ -0,0 +1,11 @@ +{ + "source_scenario": "r7-w5a-run1", + "claude_version": "2.1.285 (Claude Code)", + "platform": "darwin", + "cols": 120, + "rows": 40, + "recorded_at": "2026-09-30", + "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", + "note": "Claude launches a one-agent background workflow (`sleep 40.07`) and the turn ends (its Stop lists it). One Ctrl+C at the idle prompt while the agent runs: Claude paints \"Press Ctrl-C again to exit\", writes no row and sends no hook; the sleep keeps running and the workflow completes normally (end row, completed; then a report turn).", + "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (ba); each workflow agent id by a same-shape placeholder aba…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped)." +} diff --git a/src/shared/__fixtures__/claude-background-workflow-pause-hooks.jsonl b/src/shared/__fixtures__/claude-background-workflow-pause-hooks.jsonl new file mode 100644 index 000000000000..ff707a2e2c64 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-pause-hooks.jsonl @@ -0,0 +1,22 @@ +{"t": 0.0, "kind": "hook", "index": 0, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-opus-5-5[1m]"}} +{"t": 4.781, "kind": "key", "label": "enter-w6b"} +{"t": 4.834, "kind": "hook", "index": 1, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000001", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 1: call the Workflow tool exactly once, passing this JavaScript verbatim as its script parameter (inline, do not write it to a file): export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 120.07 . Then reply with exactly the word ONE.', {label: 'first'}); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b]; After it is launched, reply with exactly the word LAUNCHED and stop. Do not wait for it."}} +{"t": 9.049, "kind": "hook", "index": 2, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 120.07 . Then reply with exactly the word ONE.', {label: 'first'}); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_use_id": "toolu_01RvzBmWdjCLbLnf1ALaHBqM"}} +{"t": 9.149, "kind": "hook", "index": 3, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 120.07 . Then reply with exactly the word ONE.', {label: 'first'}); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_response": {"status": "async_launched", "taskId": "wmucfg2gk", "taskType": "local_workflow", "workflowName": "capture-gap", "runId": "wf_1e842583-060", "summary": "Two agents in sequence", "transcriptDir": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000/subagents/workflows/wf_1e842583-060", "scriptPath": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000/workflows/scripts/capture-gap-wf_1e842583-060.js"}, "tool_use_id": "toolu_01RvzBmWdjCLbLnf1ALaHBqM", "duration_ms": 3}} +{"t": 9.163, "kind": "hook", "index": 4, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000001", "agent_id": "abc00000000000001", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 9.255, "kind": "hook", "index": 5, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 120.07 . Then reply with exactly the word ONE.', {label: 'first'}); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_use_id": "toolu_01RvzBmWdjCLbLnf1ALaHBqM", "tool_response": "Workflow launched in background. Task ID: wmucfg2gk\nSummary: Two agents in sequence\nTranscript dir: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000/subagents/workflows/wf_1e842583-060\nScript file: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000/workflows/scripts/capture-gap-wf_1e842583-060.js\n(Edit this file with Write/Edit and re-invoke Workflow with {scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000/workflows/scripts/capture-gap-wf_1e842583-060.js\"} to iterate without resending the script.)\nRun ID: wf_1e842583-060\nTo resume after editing the script: Workflow({scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000/workflows/scripts/capture-gap-wf_1e842583-060.js\", resumeFromRunId: \"wf_1e842583-060\"}) — completed agents return cached results (cached results may themselves be empty — inspect journal.jsonl before assuming there is something to recover).\n\nYou will be notified when it completes. Use /workflows to watch live progress."}]}} +{"t": 10.584, "kind": "hook", "index": 6, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "LAUNCHED", "background_tasks": [{"id": "wmucfg2gk", "type": "workflow", "status": "running", "description": "Two agents in sequence", "name": "capture-gap"}], "session_crons": []}} +{"t": 10.584, "kind": "hook", "index": 7, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000001", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000bc000002", "message_id": "00000000-0000-4000-8000-0000bc000003", "index": 0, "final": true, "delta": "LAUNCHED"}} +{"t": 11.634, "kind": "hook", "index": 8, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000001", "permission_mode": "bypassPermissions", "agent_id": "abc00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 120.07", "timeout": 200000, "description": "Sleep for about 120 seconds"}, "tool_use_id": "toolu_01BJUh1T8ZATnq4v3dGyhV1g"}} +{"t": 31.763, "kind": "key", "label": "W6B-type-tasks"} +{"t": 43.473, "kind": "key", "label": "W6B-enter-tasks"} +{"t": 55.977, "kind": "key", "label": "W6B-p-pause"} +{"t": 137.838, "kind": "key", "label": "W6B-p-again"} +{"t": 194.03, "kind": "key", "label": "W6B-esc-close-panel"} +{"t": 452.539, "kind": "key", "label": "enter-ok2"} +{"t": 452.587, "kind": "hook", "index": 9, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000004", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 2: reply with exactly the word OK. Do not use any tools."}} +{"t": 454.228, "kind": "hook", "index": 10, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000004", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000bc000005", "message_id": "00000000-0000-4000-8000-0000bc000006", "index": 0, "final": true, "delta": "OK"}} +{"t": 454.228, "kind": "hook", "index": 11, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000004", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "OK", "background_tasks": [], "session_crons": []}} +{"t": 458.525, "kind": "key", "label": "enter-exit"} +{"t": 461.625, "kind": "key", "label": "confirm-exit-default"} +{"t": 461.923, "kind": "hook", "index": 12, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bc000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bc000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bc000007", "hook_event_name": "SessionEnd", "reason": "prompt_input_exit"}} diff --git a/src/shared/__fixtures__/claude-background-workflow-pause-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-pause-hooks.meta.json new file mode 100644 index 000000000000..873668693079 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-pause-hooks.meta.json @@ -0,0 +1,11 @@ +{ + "source_scenario": "r7-w6b-run2", + "claude_version": "2.1.285 (Claude Code)", + "platform": "darwin", + "cols": 120, + "rows": 40, + "recorded_at": "2026-09-30", + "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", + "note": "Claude launches a two-agent background workflow (agent 1 runs `sleep 120.07`) and the turn ends (its Stop lists it). The driver opens /tasks and pauses it with `p` while agent 1 runs: the pause kills the agent with no SubagentStop, and the panel reads \"paused\" with no resume key. No hook, no main-transcript row for 6.6 min. The next typed turn's Stop lists nothing; Claude's /exit dialog still lists the workflow as running.", + "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (bc); each workflow agent id by a same-shape placeholder abc…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped)." +} diff --git a/src/shared/__fixtures__/claude-background-workflow-script-wait-hooks.jsonl b/src/shared/__fixtures__/claude-background-workflow-script-wait-hooks.jsonl new file mode 100644 index 000000000000..44f13c17f11f --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-script-wait-hooks.jsonl @@ -0,0 +1,29 @@ +{"t": 0.0, "kind": "hook", "index": 0, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-opus-5-5[1m]"}} +{"t": 4.805, "kind": "key", "label": "enter-w6a"} +{"t": 4.859, "kind": "hook", "index": 1, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 1: call the Workflow tool exactly once, passing this JavaScript verbatim as its script parameter (inline, do not write it to a file): export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word ONE.', {label: 'first'}); log('waiting 20 s'); await new Promise(r => setTimeout(r, 20000)); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b]; After it is launched, reply with exactly the word LAUNCHED and stop. Do not wait for it."}} +{"t": 8.512, "kind": "hook", "index": 2, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word ONE.', {label: 'first'}); log('waiting 20 s'); await new Promise(r => setTimeout(r, 20000)); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_use_id": "toolu_01W8BksPukrKgHmBdQ27TtnU"}} +{"t": 8.648, "kind": "hook", "index": 3, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word ONE.', {label: 'first'}); log('waiting 20 s'); await new Promise(r => setTimeout(r, 20000)); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_response": {"status": "async_launched", "taskId": "wc51x4l58", "taskType": "local_workflow", "workflowName": "capture-gap", "runId": "wf_013b165a-7ca", "summary": "Two agents in sequence", "transcriptDir": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/subagents/workflows/wf_013b165a-7ca", "scriptPath": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/workflows/scripts/capture-gap-wf_013b165a-7ca.js"}, "tool_use_id": "toolu_01W8BksPukrKgHmBdQ27TtnU", "duration_ms": 3}} +{"t": 8.657, "kind": "hook", "index": 4, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "agent_id": "abb00000000000001", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 8.775, "kind": "hook", "index": 5, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word ONE.', {label: 'first'}); log('waiting 20 s'); await new Promise(r => setTimeout(r, 20000)); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_use_id": "toolu_01W8BksPukrKgHmBdQ27TtnU", "tool_response": "Workflow launched in background. Task ID: wc51x4l58\nSummary: Two agents in sequence\nTranscript dir: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/subagents/workflows/wf_013b165a-7ca\nScript file: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/workflows/scripts/capture-gap-wf_013b165a-7ca.js\n(Edit this file with Write/Edit and re-invoke Workflow with {scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/workflows/scripts/capture-gap-wf_013b165a-7ca.js\"} to iterate without resending the script.)\nRun ID: wf_013b165a-7ca\nTo resume after editing the script: Workflow({scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/workflows/scripts/capture-gap-wf_013b165a-7ca.js\", resumeFromRunId: \"wf_013b165a-7ca\"}) — completed agents return cached results (cached results may themselves be empty — inspect journal.jsonl before assuming there is something to recover).\n\nYou will be notified when it completes. Use /workflows to watch live progress."}]}} +{"t": 10.034, "kind": "hook", "index": 6, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "LAUNCHED", "background_tasks": [{"id": "wc51x4l58", "type": "workflow", "status": "running", "description": "Two agents in sequence", "name": "capture-gap"}], "session_crons": []}} +{"t": 10.034, "kind": "hook", "index": 7, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000bb000002", "message_id": "00000000-0000-4000-8000-0000bb000003", "index": 0, "final": true, "delta": "LAUNCHED"}} +{"t": 10.688, "kind": "hook", "index": 8, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "agent_id": "abb00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_use_id": "toolu_01JhAis2K7UYEoj8Hivdrwdv"}} +{"t": 15.943, "kind": "hook", "index": 9, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "agent_id": "abb00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_response": {"stdout": "", "stderr": "", "interrupted": false, "isImage": false, "noOutputExpected": false}, "tool_use_id": "toolu_01JhAis2K7UYEoj8Hivdrwdv", "duration_ms": 5130}} +{"t": 16.039, "kind": "hook", "index": 10, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "agent_id": "abb00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_use_id": "toolu_01JhAis2K7UYEoj8Hivdrwdv", "tool_response": "(Bash completed with no output)"}]}} +{"t": 17.071, "kind": "hook", "index": 11, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "agent_id": "abb00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "SubagentStop", "stop_hook_active": false, "agent_transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/subagents/workflows/wf_013b165a-7ca/agent-abb00000000000001.jsonl", "last_assistant_message": "ONE", "background_tasks": [{"id": "wc51x4l58", "type": "workflow", "status": "running", "description": "Two agents in sequence", "name": "capture-gap"}], "session_crons": []}} +{"t": 37.199, "kind": "hook", "index": 12, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "agent_id": "abb00000000000002", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 39.339, "kind": "hook", "index": 13, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "agent_id": "abb00000000000002", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_use_id": "toolu_01Lr8sPsHTqhrg5JREnH84ws"}} +{"t": 44.512, "kind": "hook", "index": 14, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "agent_id": "abb00000000000002", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_response": {"stdout": "", "stderr": "", "interrupted": false, "isImage": false, "noOutputExpected": false}, "tool_use_id": "toolu_01Lr8sPsHTqhrg5JREnH84ws", "duration_ms": 5082}} +{"t": 44.592, "kind": "hook", "index": 15, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "agent_id": "abb00000000000002", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_use_id": "toolu_01Lr8sPsHTqhrg5JREnH84ws", "tool_response": "(Bash completed with no output)"}]}} +{"t": 45.55, "kind": "hook", "index": 16, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000001", "permission_mode": "bypassPermissions", "agent_id": "abb00000000000002", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "SubagentStop", "stop_hook_active": false, "agent_transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/subagents/workflows/wf_013b165a-7ca/agent-abb00000000000002.jsonl", "last_assistant_message": "TWO", "background_tasks": [{"id": "wc51x4l58", "type": "workflow", "status": "running", "description": "Two agents in sequence", "name": "capture-gap"}], "session_crons": []}} +{"t": 45.642, "kind": "hook", "index": 17, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000004", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "\nwc51x4l58\ntoolu_01W8BksPukrKgHmBdQ27TtnU\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/tasks/wc51x4l58.output\ncompleted\nDynamic workflow \"Two agents in sequence\" completed\n[\"ONE\",\"TWO\"]\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/subagents/workflows/wf_013b165a-7ca/journal.jsonl — one {\"type\":\"result\",...} line per completed agent with its full return value.\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/workflows/scripts/capture-gap-wf_013b165a-7ca.js', resumeFromRunId: 'wf_013b165a-7ca'}) — agents whose (prompt, opts) are unchanged replay from cache.\n2200018678236999\n"}} +{"t": 45.716, "kind": "transcript", "label": "queue-operation-enqueue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"enqueue\",\"timestamp\":\"2026-09-30T18:06:08.108Z\",\"sessionId\":\"00000000-0000-4000-8000-0000bb000000\",\"content\":\"\\nwc51x4l58\\ntoolu_01W8BksPukrKgHmBdQ27TtnU\\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/tasks/wc51x4l58.output\\ncompleted\\nDynamic workflow \\\"Two agents in sequence\\\" completed\\n[\\\"ONE\\\",\\\"TWO\\\"]\\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/subagents/workflows/wf_013b165a-7ca/journal.jsonl — one {\\\"type\\\":\\\"result\\\",...} line per completed agent with its full return value.\\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000/workflows/scripts/capture-gap-wf_013b165a-7ca.js', resumeFromRunId: 'wf_013b165a-7ca'}) — agents whose (prompt, opts) are unchanged replay from cache.\\n2200018678236999\\n\"}"]} +{"t": 45.716, "kind": "transcript", "label": "queue-operation-dequeue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"dequeue\",\"timestamp\":\"2026-09-30T18:06:08.111Z\",\"sessionId\":\"00000000-0000-4000-8000-0000bb000000\"}"]} +{"t": 48.224, "kind": "hook", "index": 18, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000004", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "The workflow finished and returned `[\"ONE\",\"TWO\"]`. Both agents completed with no errors, and the whole run took about 37 seconds.", "background_tasks": [], "session_crons": []}} +{"t": 48.224, "kind": "hook", "index": 19, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000004", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000bb000005", "message_id": "00000000-0000-4000-8000-0000bb000006", "index": 0, "final": true, "delta": "The workflow finished and returned `[\"ONE\",\"TWO\"]`. Both agents completed with no errors, and the whole run took about 37 seconds."}} +{"t": 52.377, "kind": "key", "label": "enter-ok2"} +{"t": 52.423, "kind": "hook", "index": 20, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000007", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 2: reply with exactly the word OK. Do not use any tools."}} +{"t": 54.221, "kind": "hook", "index": 21, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000007", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000bb000008", "message_id": "00000000-0000-4000-8000-0000bb000009", "index": 0, "final": true, "delta": "OK"}} +{"t": 54.221, "kind": "hook", "index": 22, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb000007", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "OK", "background_tasks": [], "session_crons": []}} +{"t": 58.52, "kind": "key", "label": "enter-exit"} +{"t": 58.864, "kind": "hook", "index": 23, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bb000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bb000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bb00000a", "hook_event_name": "SessionEnd", "reason": "prompt_input_exit"}} diff --git a/src/shared/__fixtures__/claude-background-workflow-script-wait-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-script-wait-hooks.meta.json new file mode 100644 index 000000000000..7a84fc0bb0d6 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-script-wait-hooks.meta.json @@ -0,0 +1,11 @@ +{ + "source_scenario": "r7-w6a-run1", + "claude_version": "2.1.285 (Claude Code)", + "platform": "darwin", + "cols": 120, + "rows": 40, + "recorded_at": "2026-09-30", + "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", + "note": "Claude launches a two-agent background workflow whose script waits 20 s between the agents (`setTimeout`), and the turn ends normally (its Stop lists it). Agent 1's SubagentStop, then 20.1 s with no agent, no hook and no main-transcript row, then agent 2's SubagentStart. Write order by Claude's own row timestamp: end row 18:06:08.108Z; the report turn's UserPromptSubmit hook is 40 ms later (the fixture `t` of the line is the rig's poll time, which lands after the hook).", + "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (bb); each workflow agent id by a same-shape placeholder abb…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped)." +} diff --git a/src/shared/__fixtures__/claude-background-workflow-tasks-kill-hooks.jsonl b/src/shared/__fixtures__/claude-background-workflow-tasks-kill-hooks.jsonl new file mode 100644 index 000000000000..d2463fac9655 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-tasks-kill-hooks.jsonl @@ -0,0 +1,20 @@ +{"t": 0.0, "kind": "hook", "index": 0, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-opus-5-5[1m]"}} +{"t": 4.666, "kind": "key", "label": "enter-w3"} +{"t": 4.71, "kind": "hook", "index": 1, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000001", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 1: call the Workflow tool exactly once, passing this JavaScript verbatim as its script parameter (inline, do not write it to a file): export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 150.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r; After it is launched, reply with exactly the word LAUNCHED and stop. Do not wait for it."}} +{"t": 7.409, "kind": "hook", "index": 2, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 150.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_01AW6LVjKhKDY7fcsia91qaD"}} +{"t": 7.523, "kind": "hook", "index": 3, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 150.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_response": {"status": "async_launched", "taskId": "wc6rxzj9v", "taskType": "local_workflow", "workflowName": "capture-sleep", "runId": "wf_ddc86b0b-9a7", "summary": "One agent runs a timed sleep", "transcriptDir": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000/subagents/workflows/wf_ddc86b0b-9a7", "scriptPath": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000/workflows/scripts/capture-sleep-wf_ddc86b0b-9a7.js"}, "tool_use_id": "toolu_01AW6LVjKhKDY7fcsia91qaD", "duration_ms": 1}} +{"t": 7.538, "kind": "hook", "index": 4, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000001", "agent_id": "ab800000000000001", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 7.644, "kind": "hook", "index": 5, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-sleep', description: 'One agent runs a timed sleep', phases: [{ title: 'Sleep', detail: 'one agent' }] }; phase('Sleep'); const r = await agent('Use the Bash tool to run exactly: sleep 150.07 . Then reply with exactly the word SLEPT.', {label: 'sleeper'}); return r;"}, "tool_use_id": "toolu_01AW6LVjKhKDY7fcsia91qaD", "tool_response": "Workflow launched in background. Task ID: wc6rxzj9v\nSummary: One agent runs a timed sleep\nTranscript dir: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000/subagents/workflows/wf_ddc86b0b-9a7\nScript file: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000/workflows/scripts/capture-sleep-wf_ddc86b0b-9a7.js\n(Edit this file with Write/Edit and re-invoke Workflow with {scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000/workflows/scripts/capture-sleep-wf_ddc86b0b-9a7.js\"} to iterate without resending the script.)\nRun ID: wf_ddc86b0b-9a7\nTo resume after editing the script: Workflow({scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000/workflows/scripts/capture-sleep-wf_ddc86b0b-9a7.js\", resumeFromRunId: \"wf_ddc86b0b-9a7\"}) — completed agents return cached results (cached results may themselves be empty — inspect journal.jsonl before assuming there is something to recover).\n\nYou will be notified when it completes. Use /workflows to watch live progress."}]}} +{"t": 8.889, "kind": "hook", "index": 6, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "LAUNCHED", "background_tasks": [{"id": "wc6rxzj9v", "type": "workflow", "status": "running", "description": "One agent runs a timed sleep", "name": "capture-sleep"}], "session_crons": []}} +{"t": 8.889, "kind": "hook", "index": 7, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000001", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b8000002", "message_id": "00000000-0000-4000-8000-0000b8000003", "index": 0, "final": true, "delta": "LAUNCHED"}} +{"t": 9.771, "kind": "hook", "index": 8, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000001", "permission_mode": "bypassPermissions", "agent_id": "ab800000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 150.07", "timeout": 200000, "description": "Sleep for 150.07 seconds"}, "tool_use_id": "toolu_0154t9PkpcsNiBz5GwMckPho"}} +{"t": 53.746, "kind": "key", "label": "W3-type-tasks"} +{"t": 68.032, "kind": "key", "label": "W3-enter-tasks"} +{"t": 87.923, "kind": "key", "label": "W3-x-stop-workflow"} +{"t": 142.599, "kind": "key", "label": "W3-esc-close-panel"} +{"t": 223.124, "kind": "key", "label": "enter-ok2"} +{"t": 223.169, "kind": "hook", "index": 9, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000004", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 2: reply with exactly the word OK. Do not use any tools."}} +{"t": 224.817, "kind": "hook", "index": 10, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000004", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "OK", "background_tasks": [], "session_crons": []}} +{"t": 224.817, "kind": "hook", "index": 11, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000004", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000b8000005", "message_id": "00000000-0000-4000-8000-0000b8000006", "index": 0, "final": true, "delta": "OK"}} +{"t": 229.149, "kind": "key", "label": "enter-exit"} +{"t": 229.469, "kind": "hook", "index": 12, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000b8000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000b8000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000b8000007", "hook_event_name": "SessionEnd", "reason": "prompt_input_exit"}} diff --git a/src/shared/__fixtures__/claude-background-workflow-tasks-kill-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-tasks-kill-hooks.meta.json new file mode 100644 index 000000000000..230c7d70661d --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-tasks-kill-hooks.meta.json @@ -0,0 +1,11 @@ +{ + "source_scenario": "r6-w3-run1", + "claude_version": "2.1.285 (Claude Code)", + "platform": "darwin", + "cols": 120, + "rows": 40, + "recorded_at": "2026-09-30", + "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", + "note": "Claude launches a one-agent background workflow (`sleep 150.07`); the launch turn's Stop lists it. At the idle prompt the driver opens /tasks and stops the workflow with `x`. Nothing reaches the main transcript and no hook fires, not even SubagentStop. The agent's own sidechain file gets a denied tool result and an interrupt row (not kept: the watch never reads sidechains), and the run-state file says `status: killed`. The next typed turn's Stop, 135 s later, lists nothing.", + "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (b8); each workflow agent id by a same-shape placeholder ab8…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped)." +} diff --git a/src/shared/agent-hook-listener-extraction-characterization.test.ts b/src/shared/agent-hook-listener-extraction-characterization.test.ts index cd5e8a89727a..4efb9115cfa1 100644 --- a/src/shared/agent-hook-listener-extraction-characterization.test.ts +++ b/src/shared/agent-hook-listener-extraction-characterization.test.ts @@ -264,7 +264,12 @@ describe('agent hook extraction boundaries', () => { state.warnedVersions.add('old-version') state.warnedEnvs.add('development->production') state.lastPromptByPaneKey.set(PANE, 'prompt') - recordClaudeNonAgentTaskLaunch(state, PANE, 'btest0001', 'toolu_test') + recordClaudeNonAgentTaskLaunch( + state, + PANE, + { taskId: 'btest0001', kind: 'unknown' }, + 'toolu_test' + ) state.codexLeadStateByPaneKey.set(PANE, { state: 'working', stateStartedAt: 1 }) state.grokActiveTurnByPaneKey.set(PANE, { promptId: 'prompt-1' }) diff --git a/src/shared/agent-hook-listener/providers/claude-events.ts b/src/shared/agent-hook-listener/providers/claude-events.ts index 4b4dcf3ae737..e43fb2ccb793 100644 --- a/src/shared/agent-hook-listener/providers/claude-events.ts +++ b/src/shared/agent-hook-listener/providers/claude-events.ts @@ -28,6 +28,7 @@ import { import { buildClaudeStatusPayload } from './claude-status-build' import { claudePaneHasNonAgentWork, + readClaudeBackgroundTaskLaunch, recordClaudeNonAgentTaskLaunch, replaceClaudeNonAgentWorkFromInventory, retireClaudeNonAgentTask @@ -41,8 +42,8 @@ const CLAUDE_TASK_STOP_TOOL_NAMES: ReadonlySet = new Set([ ]) /** A tool result that starts or stops a background task. Only the main agent's own launch is - * recorded: a subagent's shell is its own work. A stop is Claude's confirmed result naming the - * task, whoever called it, and only a task the record holds is retired. */ + * recorded: a subagent's shell or workflow is its own work. A stop is Claude's confirmed result + * naming the task, whoever called it, and only a task the record holds is retired. */ function applyClaudeBackgroundTaskToolResult( state: HookListenerState, paneKey: string, @@ -61,13 +62,9 @@ function applyClaudeBackgroundTaskToolResult( } return } - if (eventAgentId === undefined && response.backgroundTaskId !== undefined) { - recordClaudeNonAgentTaskLaunch( - state, - paneKey, - response.backgroundTaskId, - readString(hookPayload, 'tool_use_id') - ) + const launch = eventAgentId === undefined ? readClaudeBackgroundTaskLaunch(response) : undefined + if (launch) { + recordClaudeNonAgentTaskLaunch(state, paneKey, launch, readString(hookPayload, 'tool_use_id')) } } @@ -107,8 +104,8 @@ export function normalizeClaudeEvent( } // Why: a new session owns the pane; stale children/crons must not gate its idle row back up to // 'working' (same reset Codex does on SessionStart). A new process can never end its - // predecessor's background tasks either, but /clear keeps the process, whose shells report - // their end in the new session's transcript (r3-clear-run1). + // predecessor's background tasks either, but /clear keeps the process, whose shells and + // workflows report their end in the new session's transcript (r3-clear-run1, r6-w4-run1). const keepsTasks = sessionStartSource === 'clear' state.claudeSubagentRosterByPaneKey.delete(paneKey) state.claudeActiveSessionCronPaneKeys.delete(paneKey) @@ -116,8 +113,8 @@ export function normalizeClaudeEvent( state.claudeNonAgentWorkByPaneKey.delete(paneKey) } const previousLead = state.claudeLeadStateByPaneKey.get(paneKey) - // Why: a shell that outlives /clear keeps the pane on the tail of the last turn that ended, so - // that turn's verdict and stamp stay and the shell's end is not announced as a new turn. + // Why: a task that outlives /clear keeps the pane on the tail of the last turn that ended, so + // that turn's verdict and stamp stay and the task's end is not announced as a new turn. // Otherwise a new session's main agent starts its own clock, not the old session's last Stop. const record = keepsTasks && previousLead?.state === 'done' && claudePaneHasNonAgentWork(state, paneKey) diff --git a/src/shared/agent-hook-listener/providers/claude-non-agent-work.test.ts b/src/shared/agent-hook-listener/providers/claude-non-agent-work.test.ts index 0ee23788943f..7872dea05cc0 100644 --- a/src/shared/agent-hook-listener/providers/claude-non-agent-work.test.ts +++ b/src/shared/agent-hook-listener/providers/claude-non-agent-work.test.ts @@ -41,6 +41,34 @@ const TASK_STOP = { tool_use_id: 'toolu_01BYd1WsdTmm53R5kToRn4oa' } +// Captured (r6-w1-run1, Claude 2.1.285): a background workflow's launching call and its result. +const WORKFLOW_LAUNCH = { + hook_event_name: 'PostToolUse', + tool_name: 'Workflow', + tool_input: { script: "export const meta = { name: 'capture-sleep' }" }, + tool_response: { + status: 'async_launched', + taskId: 'w1kuktaid', + taskType: 'local_workflow', + workflowName: 'capture-sleep', + runId: 'wf_ca44ff70-bf9', + summary: 'One agent runs a timed sleep' + }, + tool_use_id: 'toolu_01S3s8SyG9VLFN3QhiERnGmU' +} +// Captured (claude-cancel-subagent-hooks, Claude 2.1.280): a background Agent call's result. +const AGENT_LAUNCH = { + hook_event_name: 'PostToolUse', + tool_name: 'Agent', + tool_response: { + isAsync: true, + status: 'async_launched', + agentId: 'a89b41394dcd804b3', + description: 'Run 120s sleep command' + }, + tool_use_id: 'toolu_01AgentLaunchAAAAAAAAAAA' +} + function claudeEvent(state: HookListenerState, payload: Record) { return normalizeHookPayload(state, 'claude', { paneKey: PANE, payload }, 'production') } @@ -158,6 +186,86 @@ describe('the Claude background task record', () => { }) }) +describe('what a launch records', () => { + function recordsFrom(payload: Record) { + const state = createHookListenerState() + claudeEvent(state, { hook_event_name: 'UserPromptSubmit', prompt: 'start it' }) + claudeEvent(state, payload) + return state.claudeNonAgentWorkByPaneKey.get(PANE)?.tasks + } + + function workflowLaunchWith(response: Record) { + return { ...WORKFLOW_LAUNCH, tool_response: response } + } + + it('records a workflow from its async launch, typed from its taskType', () => { + expect(recordsFrom(WORKFLOW_LAUNCH)).toEqual( + new Map([['w1kuktaid', { kind: 'workflow', launchToolUseId: WORKFLOW_LAUNCH.tool_use_id }]]) + ) + }) + + it('records a shell from backgroundTaskId, its kind left for an inventory to type', () => { + expect(recordsFrom(LAUNCH)?.get('bjomx789i')).toEqual({ + kind: 'unknown', + launchToolUseId: LAUNCH.tool_use_id + }) + }) + + // Hand-built from the captured workflow launch: no capture has these kinds' launch results. + it('records any kind the table does not call an agent, unknown ones included, as the inventory does', () => { + const { taskType: _captured, ...untyped } = WORKFLOW_LAUNCH.tool_response + for (const response of [ + { ...WORKFLOW_LAUNCH.tool_response, taskType: 'remote_agent' }, + { ...WORKFLOW_LAUNCH.tool_response, taskType: 'monitor_ws' }, + untyped + ]) { + expect(recordsFrom(workflowLaunchWith(response))?.get('w1kuktaid')?.kind).toBe('unknown') + } + }) + + // Temporary: the shared table calls `in_process_teammate` unknown; its fix is its own change. + it('records a teammate launch naming a taskId (hand-built, uncaptured) until the next inventory', () => { + const teammate = { ...WORKFLOW_LAUNCH.tool_response, taskType: 'in_process_teammate' } + expect(recordsFrom(workflowLaunchWith(teammate))?.get('w1kuktaid')?.kind).toBe('unknown') + }) + + it('leaves agents to the rosters', () => { + expect(recordsFrom(AGENT_LAUNCH)).toBeUndefined() + // Hand-built: a future launch that names an agent by taskId. + for (const taskType of ['local_agent', 'subagent']) { + const agent = { ...WORKFLOW_LAUNCH.tool_response, taskType } + expect(recordsFrom(workflowLaunchWith(agent))).toBeUndefined() + } + }) + + // Hand-built: a taskId outside Claude's background-launch result, from any other tool. + it('ignores a taskId that is not an async launch', () => { + const { status: _captured, ...unmarked } = WORKFLOW_LAUNCH.tool_response + expect(recordsFrom(workflowLaunchWith(unmarked))).toBeUndefined() + const finished = { ...WORKFLOW_LAUNCH.tool_response, status: 'completed' } + expect(recordsFrom(workflowLaunchWith(finished))).toBeUndefined() + }) + + it("ignores a subagent's workflow launch", () => { + expect( + recordsFrom({ ...WORKFLOW_LAUNCH, agent_id: 'a1', agent_type: 'general-purpose' }) + ).toBeUndefined() + }) + + it('keeps the kind an inventory already gave the task', () => { + const state = createHookListenerState() + claudeEvent(state, { + hook_event_name: 'Stop', + background_tasks: [{ id: 'w1kuktaid', type: 'monitor', status: 'running' }] + }) + claudeEvent(state, WORKFLOW_LAUNCH) + expect(state.claudeNonAgentWorkByPaneKey.get(PANE)?.tasks.get('w1kuktaid')).toEqual({ + kind: 'monitor', + launchToolUseId: WORKFLOW_LAUNCH.tool_use_id + }) + }) +}) + describe('the task end line in the transcript', () => { it('retires the task only for its launch-recorded task id and tool call', () => { const state = launched() @@ -189,6 +297,46 @@ describe('the task end line in the transcript', () => { expect(claudePaneHasLaunchRecordedTask(state, PANE)).toBe(true) }) + // Hand-built from the captured workflow end row (r6-w1-run1), whose `` is the agents' + // own text: nothing in it may end the task early or retire another one. + it('reads a workflow end row by the fields before its agent-written result', () => { + const state = createHookListenerState() + claudeEvent(state, { hook_event_name: 'UserPromptSubmit', prompt: 'start them' }) + claudeEvent(state, WORKFLOW_LAUNCH) + claudeEvent(state, { + ...WORKFLOW_LAUNCH, + tool_response: { ...WORKFLOW_LAUNCH.tool_response, taskId: 'wsecond01' }, + tool_use_id: 'toolu_01SecondWorkflowAAAAAAA' + }) + const workflowEnd = (result: string) => ({ + type: 'queue-operation', + operation: 'enqueue', + timestamp: '2026-09-30T17:00:28.690Z', + content: `\nw1kuktaid\n${WORKFLOW_LAUNCH.tool_use_id}\ncompleted\nDynamic workflow "One agent runs a timed sleep" completed\n${result}\n` + }) + expect( + retireClaudeNonAgentTaskFromQueueRow( + state, + PANE, + workflowEnd('"w1kuktaid"') + ) + ).toBe(false) + expect(state.claudeNonAgentWorkByPaneKey.get(PANE)?.tasks.size).toBe(2) + + expect( + retireClaudeNonAgentTaskFromQueueRow( + state, + PANE, + workflowEnd( + '"wsecond01toolu_01SecondWorkflowAAAAAAA"' + ) + ) + ).toBe(true) + expect([...(state.claudeNonAgentWorkByPaneKey.get(PANE)?.tasks.keys() ?? [])]).toEqual([ + 'wsecond01' + ]) + }) + it('leaves a task only an inventory named (no recorded launch) for the next inventory', () => { const state = createHookListenerState() claudeEvent(state, { diff --git a/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts b/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts index c5d2c7c55bc7..2f35d468e4a5 100644 --- a/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts +++ b/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts @@ -2,10 +2,14 @@ // Claude's own task id. It starts at the tool call that launches the task or at the inventory Claude // attaches to Stop, and ends at Claude's own record of the task's end: the next inventory, a // TaskStop result, or the task-notification row Claude writes to its transcript when the task -// ends. A new process's session start ends them too; /clear does not, since a shell outlives it. +// ends. A new process's session start ends them too; /clear does not, since the task outlives it. import type { AgentChildWorkKind } from '../../agent-status-child-work' +import { isAgentChildWorkKind } from '../../agent-status-child-work-liveness' import type { ClaudeBackgroundNonAgentTask } from '../../claude-background-task-inventory' -import { isClaudeBackgroundTaskStatusTerminal } from '../../claude-background-task-kind' +import { + classifyClaudeBackgroundTaskKind, + isClaudeBackgroundTaskStatusTerminal +} from '../../claude-background-task-kind' import { readJsonlCursor } from '../../codex-rollout-jsonl-cursor' import type { AgentHookEventPayload } from '../listener-event' import type { HookListenerState } from '../listener-state' @@ -70,21 +74,44 @@ export function claudePaneHasLaunchRecordedTask( return tasks !== undefined && [...tasks.values()].some((task) => task.launchToolUseId) } -/** The main agent's launching tool call named a background task (`tool_response.backgroundTaskId`). - * Its kind is unknown until an inventory types it, which fails active. */ +export type ClaudeBackgroundTaskLaunch = { taskId: unknown; kind: AgentChildWorkKind } + +/** The background task a tool result launched, if any. A shell names it `backgroundTaskId`, its + * kind unknown until an inventory types it (r1-s1). Any other kind is Claude's `async_launched` + * result naming a `taskId` and a `taskType` (a workflow, r6-w1..w4), admitted as the inventory + * admits it: every kind the table does not call an agent, unknown ones included, since agents are + * the rosters' and an unknown kind fails active. */ +export function readClaudeBackgroundTaskLaunch( + response: Record +): ClaudeBackgroundTaskLaunch | undefined { + if (response.backgroundTaskId !== undefined) { + return { taskId: response.backgroundTaskId, kind: 'unknown' } + } + if (response.status !== 'async_launched' || response.taskId === undefined) { + return undefined + } + // Temporary: the table calls `in_process_teammate` unknown, so a teammate launch naming a + // `taskId` (uncaptured) is recorded until the next inventory; the table fix is its own change. + const kind = classifyClaudeBackgroundTaskKind(response.taskType) + return isAgentChildWorkKind(kind) ? undefined : { taskId: response.taskId, kind } +} + +/** The main agent's launching tool call named a background task. A kind an inventory already gave + * it wins over the launch's. */ export function recordClaudeNonAgentTaskLaunch( state: HookListenerState, paneKey: string, - taskId: unknown, + launch: ClaudeBackgroundTaskLaunch, launchToolUseId: unknown ): void { + const { taskId } = launch if (!isClaudeTaskId(taskId)) { return } const previous = state.claudeNonAgentWorkByPaneKey.get(paneKey) const tasks = new Map(previous?.tasks) const added = addCapped(tasks, taskId, { - kind: tasks.get(taskId)?.kind ?? 'unknown', + kind: tasks.get(taskId)?.kind ?? launch.kind, ...(isClaudeTaskId(launchToolUseId) ? { launchToolUseId } : {}) }) writeClaudeNonAgentWork(state, paneKey, tasks, (previous?.hasUnnamedRunning ?? false) || !added) @@ -145,8 +172,10 @@ function readTaskNotificationField(body: string, field: string): string | undefi } /** Claude writes a `queue-operation` `enqueue` row carrying a task's notification the moment the - * task ends, however it ended: finished, killed with its turn's Ctrl+C, or from /tasks, even - * while Claude idles and sends no hook (r1-s1, r1-s9, r3-tasks-run1). A prompt typed while Claude + * task ends, even while Claude idles and sends no hook: a shell however it ended (finished, killed + * with its turn's Ctrl+C or from /tasks: r1-s1, r1-s9, r3-tasks-run1), a workflow only when it + * completes (r6-w1, r6-w2; one stopped or paused from /tasks writes nothing, r6-w3, r7-w6b, so it + * leaves on the next inventory). A prompt typed while Claude * is busy writes the same row with only its text (r3-typed-run1), so the row carries no provenance * of its own: it retires a task only when it is exactly one terminal notification naming both the * task id and the tool call Orca recorded at launch, which Claude mints fresh for each launch. diff --git a/src/shared/agent-hook-listener/providers/claude-roster-state.ts b/src/shared/agent-hook-listener/providers/claude-roster-state.ts index 905ef56dd66e..44d84a2bf14f 100644 --- a/src/shared/agent-hook-listener/providers/claude-roster-state.ts +++ b/src/shared/agent-hook-listener/providers/claude-roster-state.ts @@ -39,10 +39,10 @@ const CLAUDE_SESSION_OWNER_EVENTS: ReadonlySet = new Set([ * emits SessionEnd on /clear, but Orca previously did not install it and older binaries emit none. * * Voids only what the replaced session provably owned. Deliberately NOT voided: - * - `claudeNonAgentWorkByPaneKey`: a background shell is an OS process that survives /clear - * (capture r3-clear-run1), and Claude writes its end into the new session's transcript. Claude's - * own end record, a fresh inventory, a new process's SessionStart or a certified process death - * retires it. + * - `claudeNonAgentWorkByPaneKey`: a background shell or workflow survives /clear (captures + * r3-clear-run1, r6-w4-run1), and Claude writes its end into the new session's transcript. + * Claude's own end record, a fresh inventory, a new process's SessionStart or a certified + * process death retires it. * - `confirmedTeammate` roster rows: persistent in-process teammates a lead replacement can't end. * - `claudeLeadStateByPaneKey`: the caller's own fold overwrites it anyway. */ export function voidClaimsOfReplacedClaudeSession( diff --git a/src/shared/agent-hook-listener/providers/claude-transcript-watch.ts b/src/shared/agent-hook-listener/providers/claude-transcript-watch.ts index 0550efb3fc9e..8b93d57bb3e2 100644 --- a/src/shared/agent-hook-listener/providers/claude-transcript-watch.ts +++ b/src/shared/agent-hook-listener/providers/claude-transcript-watch.ts @@ -1,7 +1,8 @@ // Claude records some of what it does only in its session transcript, with no hook: killing every // background agent on an idle-prompt Ctrl+C (an id-less `system`/`agents_killed` row, the // claude-idle-ctrl-c-* fixtures), and the end of a background task, however it ended (a -// `queue-operation` row carrying its task notification, the claude-background-shell-* fixtures). +// `queue-operation` row carrying its task notification, the claude-background-shell-* and +// claude-background-workflow-* fixtures). // The host that runs the session keeps one cursor per pane while any reason to watch holds, reads // it before every Claude event and on a timer, applies what it reads to the listener's records as // facts, and restates the row those records now make. @@ -89,10 +90,10 @@ const CLAUDE_TRANSCRIPT_WATCH_REASONS = { admits: (line) => line.includes('"agents_killed"'), apply: applyAgentsKilled }, - /** A background task whose launch Orca recorded, which Claude can end with no hook (a /tasks - * kill, or an end the Ctrl+C that cancelled its turn never reports). Dies when the record holds - * no launch-recorded task: its end row, TaskStop, the next inventory, a new process's - * SessionStart, pane teardown. */ + /** A background task whose launch Orca recorded, which Claude can end with no hook (a shell's + * /tasks kill, or any task's end after the Ctrl+C that cancelled its turn). Dies when the + * record holds no launch-recorded task: its end row, TaskStop, the next inventory, a new + * process's SessionStart, pane teardown. */ 'recorded-task': { holds: claudePaneHasLaunchRecordedTask, admits: mayBeClaudeTaskEndLine, diff --git a/src/shared/claude-background-task-kind.ts b/src/shared/claude-background-task-kind.ts index 7b5672d652c0..7392f083724d 100644 --- a/src/shared/claude-background-task-kind.ts +++ b/src/shared/claude-background-task-kind.ts @@ -3,7 +3,8 @@ import type { AgentChildWorkKind } from './agent-status-child-work' /** * The one table of what Claude calls a task. The SDK stream names tasks * `local_*`/`monitor`; the hook payload's `background_tasks` inventory names - * them `subagent`/`teammate`/`shell`. Both lanes classify here so neither can + * them `subagent`/`teammate`/`shell`/`workflow`, and a hook's launch result + * names its `taskType` `local_*`. Every lane classifies here so none can * drift on which kinds are agents. */ export function classifyClaudeBackgroundTaskKind(taskType: unknown): AgentChildWorkKind { @@ -14,6 +15,7 @@ export function classifyClaudeBackgroundTaskKind(taskType: unknown): AgentChildW case 'teammate': return 'agent' case 'local_workflow': + case 'workflow': return 'workflow' case 'local_bash': case 'shell': diff --git a/src/shared/claude-background-task-status.test.ts b/src/shared/claude-background-task-status.test.ts index b44ad4757853..ad9d26ac7190 100644 --- a/src/shared/claude-background-task-status.test.ts +++ b/src/shared/claude-background-task-status.test.ts @@ -53,6 +53,23 @@ describe('Claude background task status', () => { ).toBe('working') }) + it("types a workflow the way the launch that named it did, from Claude's inventory name", () => { + // Captured (r6-w1-run1, Claude 2.1.285): the launch turn's Stop. + const result = readClaudeBackgroundAgentTasks({ + background_tasks: [ + { + id: 'w1kuktaid', + type: 'workflow', + status: 'running', + description: 'One agent runs a timed sleep', + name: 'capture-sleep' + } + ] + }) + expect(result.runningNonAgentTasks).toEqual([{ id: 'w1kuktaid', kind: 'workflow' }]) + expect(result.tasks).toEqual([]) + }) + it('fails unknown, partial, or malformed task entries active but ignores terminal entries', () => { for (const status of ['queued', undefined]) { expect( From b316a4fe5fa1c5aa1ce22f6bd3a41be535f7991c Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 11:57:09 -0700 Subject: [PATCH 2/6] test(agent-status): W8, a workflow whose script waits 60 s, Ctrl+C'd 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. --- ...laude-background-workflow-captures.test.ts | 68 ++++++++++++++++++- ...nd-workflow-script-wait-ctrl-c-hooks.jsonl | 30 ++++++++ ...orkflow-script-wait-ctrl-c-hooks.meta.json | 11 +++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.jsonl create mode 100644 src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.meta.json diff --git a/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts b/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts index 274172abb0bb..9c3e09753d0a 100644 --- a/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts +++ b/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts @@ -10,8 +10,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { AgentHookServer } from './server' import { _internals } from './server' import { PANE } from './server.test-fixtures' -import { cancelLabelled, hookAt, loadCapture } from './claude-cancel-capture.test-fixture' -import type { CapturedRecord } from './claude-cancel-capture.test-fixture' +import { + cancelLabelled, + hookAt, + hookSupersedesCancel, + loadCapture, + type CapturedRecord +} from './claude-cancel-capture.test-fixture' import { captureEpoch, cleanUpCaptureReplays, @@ -70,6 +75,65 @@ function keyAt(records: CapturedRecord[], label: string): number { const MONITORING = { state: 'working', workingMode: 'monitoring' } as const const CANCELLED = { mainAgent: { state: 'done', outcome: 'cancellation' } } as const +// W8, the case this change is for: after a Ctrl+C in the launch turn nothing but the workflow's own +// record says it runs while its script waits between agents. +describe('Ctrl+C in the turn that launched a workflow whose script waits between agents (captured, 2.1.285)', () => { + const records = loadCapture('claude-background-workflow-script-wait-ctrl-c-hooks') + const cancel = cancelLabelled(records, 'W8-ctrl-c-after-launch') + const notice = queueLine(records, 'interrupt-notice') + const completed = queueLine(records, 'queue-operation-enqueue') + + it('monitors through the 60 s wait with no agent, instead of reading done', async () => { + const server = await startServer() + const transcript = transcriptFile() + const replay = replayer(server, records, new Map([['*', transcript]])) + // Hook order by the capture's clock: the launch result lands 11 ms before agent 1 starts. + await replay([0, 1, 2, 4, 3, 5]) + expect(recordedTasks(server)?.get('w1mq9q8uo')).toEqual({ + kind: 'workflow', + launchToolUseId: 'toolu_01TQmzzuafz2UhRsCBJgZWEe' + }) + + // The capture: no hook inside the renderer's settle window, so it infers the Ctrl+C. + expect(cancel).toMatchObject({ interrupted_painted: true }) + expect(hookSupersedesCancel(records, cancel)).toBe(false) + vi.setSystemTime(captureEpoch(records) + cancel.t * 1000) + expect(pressCtrlC(server)).toBe(true) + appendFileSync(transcript, `${notice.lines[0]}\n`) + await replay([6, 7, 8]) + expect(row(server)).toMatchObject({ state: 'working', ...CANCELLED }) + + // Agent 1 stops; agent 2 starts 60.1 s later, with no hook or main-transcript line between. + await replay([9]) + expect(rosterSize(server)).toBe(0) + expect(row(server)).toMatchObject({ ...MONITORING, ...CANCELLED }) + expect(row(server).interrupted).toBeUndefined() + expect(hookAt(records, 10).t - hookAt(records, 9).t).toBeGreaterThan(60) + vi.setSystemTime(captureEpoch(records) + (hookAt(records, 10).t - 1) * 1000) + // Hand-built: a line no reason reads, so a caught-up offset proves a tick ran late in the wait. + appendFileSync(transcript, '{"type":"mode"}\n') + await watchCaughtUp(server, transcript) + expect(row(server)).toMatchObject({ ...MONITORING, ...CANCELLED }) + + await replay([10, 11]) + expect(row(server)).toMatchObject({ state: 'working', ...CANCELLED }) + expect(row(server).workingMode).not.toBe('monitoring') + await replay([12, 13, 14]) + expect(row(server)).toMatchObject({ ...MONITORING, ...CANCELLED }) + + // By Claude's own stamps the end line (18:43:23.636Z) precedes the report turn's prompt hook + // by 41 ms. + writeCaptured(records, completed, transcript) + await vi.waitFor(() => expect(row(server).state).toBe('done'), { timeout: 3_000 }) + expect(row(server)).toMatchObject({ interrupted: true, ...CANCELLED }) + expect(recordedTasks(server)).toBeUndefined() + await replay([15]) + expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) + await replay([16, 17, 18]) + expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + }) +}) + describe('Ctrl+C in the turn that launched a background workflow (captured, 2.1.285)', () => { const records = loadCapture('claude-background-workflow-ctrl-c-hooks') const cancel = cancelLabelled(records, 'W2-ctrl-c-mid-essay') diff --git a/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.jsonl b/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.jsonl new file mode 100644 index 000000000000..59958422da4c --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.jsonl @@ -0,0 +1,30 @@ +{"t": 0.0, "kind": "hook", "index": 0, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "hook_event_name": "SessionStart", "source": "startup", "model": "claude-opus-5-5[1m]"}} +{"t": 4.94, "kind": "key", "label": "enter-w8"} +{"t": 5.0, "kind": "hook", "index": 1, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 1: call the Workflow tool exactly once, passing this JavaScript verbatim as its script parameter (inline, do not write it to a file): export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word ONE.', {label: 'first'}); log('waiting 60 s'); await new Promise(r => setTimeout(r, 60000)); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b]; Right after it is launched, without using any more tools and without waiting for it, write an 800-word essay about lighthouses."}} +{"t": 8.522, "kind": "hook", "index": 2, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word ONE.', {label: 'first'}); log('waiting 60 s'); await new Promise(r => setTimeout(r, 60000)); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_use_id": "toolu_01TQmzzuafz2UhRsCBJgZWEe"}} +{"t": 8.622, "kind": "hook", "index": 4, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word ONE.', {label: 'first'}); log('waiting 60 s'); await new Promise(r => setTimeout(r, 60000)); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_response": {"status": "async_launched", "taskId": "w1mq9q8uo", "taskType": "local_workflow", "workflowName": "capture-gap", "runId": "wf_57eb5b27-667", "summary": "Two agents in sequence", "transcriptDir": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/subagents/workflows/wf_57eb5b27-667", "scriptPath": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/workflows/scripts/capture-gap-wf_57eb5b27-667.js"}, "tool_use_id": "toolu_01TQmzzuafz2UhRsCBJgZWEe", "duration_ms": 1}} +{"t": 8.633, "kind": "hook", "index": 3, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "agent_id": "abd00000000000001", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 8.724, "kind": "hook", "index": 5, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Workflow", "tool_input": {"script": "export const meta = { name: 'capture-gap', description: 'Two agents in sequence', phases: [{ title: 'First', detail: 'agent one' }, { title: 'Second', detail: 'agent two' }] }; phase('First'); const a = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word ONE.', {label: 'first'}); log('waiting 60 s'); await new Promise(r => setTimeout(r, 60000)); phase('Second'); const b = await agent('Use the Bash tool to run exactly: sleep 5.07 . Then reply with exactly the word TWO.', {label: 'second'}); return [a, b];"}, "tool_use_id": "toolu_01TQmzzuafz2UhRsCBJgZWEe", "tool_response": "Workflow launched in background. Task ID: w1mq9q8uo\nSummary: Two agents in sequence\nTranscript dir: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/subagents/workflows/wf_57eb5b27-667\nScript file: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/workflows/scripts/capture-gap-wf_57eb5b27-667.js\n(Edit this file with Write/Edit and re-invoke Workflow with {scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/workflows/scripts/capture-gap-wf_57eb5b27-667.js\"} to iterate without resending the script.)\nRun ID: wf_57eb5b27-667\nTo resume after editing the script: Workflow({scriptPath: \"/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/workflows/scripts/capture-gap-wf_57eb5b27-667.js\", resumeFromRunId: \"wf_57eb5b27-667\"}) — completed agents return cached results (cached results may themselves be empty — inspect journal.jsonl before assuming there is something to recover).\n\nYou will be notified when it completes. Use /workflows to watch live progress."}]}} +{"t": 10.346, "kind": "cancel", "label": "W8-ctrl-c-after-launch", "interrupted_painted": true, "exit_hint_painted": false, "hooks_before_next_typed_prompt": [6, 7, 8, 9]} +{"t": 10.481, "kind": "transcript", "label": "interrupt-notice", "lines": ["{\"parentUuid\":\"00000000-0000-4000-8000-0000bd000002\",\"isSidechain\":false,\"promptId\":\"00000000-0000-4000-8000-0000bd000001\",\"type\":\"user\",\"message\":{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"[Request interrupted by user]\"}]},\"uuid\":\"00000000-0000-4000-8000-0000bd000003\",\"timestamp\":\"2026-09-30T18:42:06.821Z\",\"interruptedMessageId\":\"msg_0000000000000000000000bd\",\"session_id\":\"00000000-0000-4000-8000-0000bd000000\",\"userType\":\"external\",\"entrypoint\":\"cli\",\"cwd\":\"/Users/uuuuu/orca-qa/r1r2-capture/work\",\"sessionId\":\"00000000-0000-4000-8000-0000bd000000\",\"version\":\"2.1.285\",\"gitBranch\":\"HEAD\"}"]} +{"t": 11.268, "kind": "hook", "index": 6, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "agent_id": "abd00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_use_id": "toolu_01UKxvkspbonygaQNdJGie5e"}} +{"t": 16.475, "kind": "hook", "index": 7, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "agent_id": "abd00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_response": {"stdout": "", "stderr": "", "interrupted": false, "isImage": false, "noOutputExpected": false}, "tool_use_id": "toolu_01UKxvkspbonygaQNdJGie5e", "duration_ms": 5106}} +{"t": 16.563, "kind": "hook", "index": 8, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "agent_id": "abd00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_use_id": "toolu_01UKxvkspbonygaQNdJGie5e", "tool_response": "(Bash completed with no output)"}]}} +{"t": 17.711, "kind": "hook", "index": 9, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "agent_id": "abd00000000000001", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "SubagentStop", "stop_hook_active": false, "agent_transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/subagents/workflows/wf_57eb5b27-667/agent-abd00000000000001.jsonl", "last_assistant_message": "ONE", "background_tasks": [{"id": "w1mq9q8uo", "type": "workflow", "status": "running", "description": "Two agents in sequence", "name": "capture-gap"}], "session_crons": []}} +{"t": 77.832, "kind": "hook", "index": 10, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "agent_id": "abd00000000000002", "agent_type": "workflow-subagent", "hook_event_name": "SubagentStart"}} +{"t": 80.554, "kind": "hook", "index": 11, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "agent_id": "abd00000000000002", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_use_id": "toolu_01CQHEBSs11gziXT7KTuxfFH"}} +{"t": 85.784, "kind": "hook", "index": 12, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "agent_id": "abd00000000000002", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolUse", "tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_response": {"stdout": "", "stderr": "", "interrupted": false, "isImage": false, "noOutputExpected": false}, "tool_use_id": "toolu_01CQHEBSs11gziXT7KTuxfFH", "duration_ms": 5086}} +{"t": 85.945, "kind": "hook", "index": 13, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "agent_id": "abd00000000000002", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "PostToolBatch", "tool_calls": [{"tool_name": "Bash", "tool_input": {"command": "sleep 5.07", "description": "Sleep for 5.07 seconds"}, "tool_use_id": "toolu_01CQHEBSs11gziXT7KTuxfFH", "tool_response": "(Bash completed with no output)"}]}} +{"t": 87.114, "kind": "hook", "index": 14, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000001", "permission_mode": "bypassPermissions", "agent_id": "abd00000000000002", "agent_type": "workflow-subagent", "effort": {"level": "medium"}, "hook_event_name": "SubagentStop", "stop_hook_active": false, "agent_transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/subagents/workflows/wf_57eb5b27-667/agent-abd00000000000002.jsonl", "last_assistant_message": "TWO", "background_tasks": [{"id": "w1mq9q8uo", "type": "workflow", "status": "running", "description": "Two agents in sequence", "name": "capture-gap"}], "session_crons": []}} +{"t": 87.199, "kind": "transcript", "label": "queue-operation-enqueue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"enqueue\",\"timestamp\":\"2026-09-30T18:43:23.636Z\",\"sessionId\":\"00000000-0000-4000-8000-0000bd000000\",\"content\":\"\\nw1mq9q8uo\\ntoolu_01TQmzzuafz2UhRsCBJgZWEe\\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/tasks/w1mq9q8uo.output\\ncompleted\\nDynamic workflow \\\"Two agents in sequence\\\" completed\\n[\\\"ONE\\\",\\\"TWO\\\"]\\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/subagents/workflows/wf_57eb5b27-667/journal.jsonl — one {\\\"type\\\":\\\"result\\\",...} line per completed agent with its full return value.\\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/workflows/scripts/capture-gap-wf_57eb5b27-667.js', resumeFromRunId: 'wf_57eb5b27-667'}) — agents whose (prompt, opts) are unchanged replay from cache.\\n2200018646278582\\n\"}"]} +{"t": 87.199, "kind": "transcript", "label": "queue-operation-dequeue", "lines": ["{\"type\":\"queue-operation\",\"operation\":\"dequeue\",\"timestamp\":\"2026-09-30T18:43:23.640Z\",\"sessionId\":\"00000000-0000-4000-8000-0000bd000000\"}"]} +{"t": 87.206, "kind": "hook", "index": 15, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000004", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "\nw1mq9q8uo\ntoolu_01TQmzzuafz2UhRsCBJgZWEe\n/private/tmp/claude-501/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/tasks/w1mq9q8uo.output\ncompleted\nDynamic workflow \"Two agents in sequence\" completed\n[\"ONE\",\"TWO\"]\nPer-agent results: /Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/subagents/workflows/wf_57eb5b27-667/journal.jsonl — one {\"type\":\"result\",...} line per completed agent with its full return value.\nIf the result above is empty or unexpected, Read this file BEFORE diagnosing — do not assume agents returned non-empty results.\nTo re-run with edited post-processing: Workflow({scriptPath: '/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000/workflows/scripts/capture-gap-wf_57eb5b27-667.js', resumeFromRunId: 'wf_57eb5b27-667'}) — agents whose (prompt, opts) are unchanged replay from cache.\n2200018646278582\n"}} +{"t": 92.114, "kind": "hook", "index": 16, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000004", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000bd000005", "message_id": "00000000-0000-4000-8000-0000bd000006", "index": 0, "final": false, "delta": "The `capture-gap` workflow finished and returned `[\"ONE\",\"TWO\"]`. Both agents ran their `sleep 5.07` command and replied as instructed, with no errors. The whole run took about 78.6 seconds, which includes the 60-second wait between the two phases.\n\n"}} +{"t": 92.127, "kind": "hook", "index": 17, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000004", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000bd000005", "message_id": "00000000-0000-4000-8000-0000bd000006", "index": 1, "final": true, "delta": "You interrupted before I started the essay about lighthouses, so it hasn't been written. Tell me if you still want it."}} +{"t": 92.135, "kind": "hook", "index": 18, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000004", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "The `capture-gap` workflow finished and returned `[\"ONE\",\"TWO\"]`. Both agents ran their `sleep 5.07` command and replied as instructed, with no errors. The whole run took about 78.6 seconds, which includes the 60-second wait between the two phases.\n\nYou interrupted before I started the essay about lighthouses, so it hasn't been written. Tell me if you still want it.", "background_tasks": [], "session_crons": []}} +{"t": 96.325, "kind": "key", "label": "enter-ok2"} +{"t": 96.367, "kind": "hook", "index": 19, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000007", "permission_mode": "bypassPermissions", "hook_event_name": "UserPromptSubmit", "prompt": "Capture step 2: reply with exactly the word OK. Do not use any tools."}} +{"t": 98.089, "kind": "hook", "index": 20, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000007", "hook_event_name": "MessageDisplay", "turn_id": "00000000-0000-4000-8000-0000bd000008", "message_id": "00000000-0000-4000-8000-0000bd000009", "index": 0, "final": true, "delta": "OK"}} +{"t": 98.089, "kind": "hook", "index": 21, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd000007", "permission_mode": "bypassPermissions", "effort": {"level": "medium"}, "hook_event_name": "Stop", "stop_hook_active": false, "last_assistant_message": "OK", "background_tasks": [], "session_crons": []}} +{"t": 102.4, "kind": "key", "label": "enter-exit"} +{"t": 102.731, "kind": "hook", "index": 22, "sleep_procs": [], "payload": {"session_id": "00000000-0000-4000-8000-0000bd000000", "transcript_path": "/Users/uuuuu/orca-qa/r1r2-capture/home/.claude/projects/-Users-uuuuu-orca-qa-r1r2-capture-work/00000000-0000-4000-8000-0000bd000000.jsonl", "cwd": "/Users/uuuuu/orca-qa/r1r2-capture/work", "prompt_id": "00000000-0000-4000-8000-0000bd00000a", "hook_event_name": "SessionEnd", "reason": "prompt_input_exit"}} diff --git a/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.meta.json new file mode 100644 index 000000000000..2fa62d9e0a31 --- /dev/null +++ b/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.meta.json @@ -0,0 +1,11 @@ +{ + "source_scenario": "r8-w8-run2", + "claude_version": "2.1.285 (Claude Code)", + "platform": "darwin", + "cols": 120, + "rows": 40, + "recorded_at": "2026-09-30", + "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", + "note": "W8: Claude launches a two-agent background workflow whose script waits 60 s between the agents (`setTimeout`), then writes a reply; Ctrl+C 1.7 s after the launch hook, in the same turn. Claude writes its interrupt notice and sends no Stop. Agent 1's hooks follow under the cancelled turn's prompt id (the first 0.92 s after the key, past the renderer's 500 ms settle window), then 60.1 s with no hook, no main-transcript row and no sidechain write, then agent 2. Write order by Claude's own row timestamp: end row 18:43:23.636Z (completed, the launch's ids); the report turn's UserPromptSubmit hook is 41 ms later. Run 2 of two identical runs: in run 1 agent 1's first hook landed 0.487 s after the key, inside the settle window, where the renderer drops the inferred Ctrl+C and every build reads Working.", + "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (bd); each workflow agent id by a same-shape placeholder abd…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped)." +} From eed18d4993919efb4bc04e1512dec56848f78aab Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:00:10 -0700 Subject: [PATCH 3/6] test(agent-status): let the workflow stories fail on what the pane shows, not on the record's contents --- ...r-claude-background-workflow-captures.test.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts b/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts index 9c3e09753d0a..b82e78924e9b 100644 --- a/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts +++ b/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts @@ -28,7 +28,6 @@ import { storedTaskFact, temporaryDir, transcriptFile, - watch, watchCaughtUp, writeCaptured } from './claude-background-task-capture.test-fixture' @@ -89,10 +88,6 @@ describe('Ctrl+C in the turn that launched a workflow whose script waits between const replay = replayer(server, records, new Map([['*', transcript]])) // Hook order by the capture's clock: the launch result lands 11 ms before agent 1 starts. await replay([0, 1, 2, 4, 3, 5]) - expect(recordedTasks(server)?.get('w1mq9q8uo')).toEqual({ - kind: 'workflow', - launchToolUseId: 'toolu_01TQmzzuafz2UhRsCBJgZWEe' - }) // The capture: no hook inside the renderer's settle window, so it infers the Ctrl+C. expect(cancel).toMatchObject({ interrupted_painted: true }) @@ -151,10 +146,6 @@ describe('Ctrl+C in the turn that launched a background workflow (captured, 2.1. taskId: 'wuxzs3s3c', taskType: 'local_workflow' }) - expect(recordedTasks(server)?.get('wuxzs3s3c')).toEqual({ - kind: 'workflow', - launchToolUseId: 'toolu_01KDk2uKk754Pvw5EpcecXru' - }) await replay([4, 5, 6, 7, 8]) expect(row(server)).toMatchObject({ state: 'working', mainAgent: { state: 'working' } }) @@ -198,16 +189,11 @@ describe('a background workflow run to completion (captured, 2.1.285)', () => { const transcript = transcriptFile() const replay = replayer(server, records, new Map([['*', transcript]])) await replay([0, 1, 2, 3]) - expect(watch(server)?.reasons).toContain('recorded-task') await replay([4, 5, 6, 7]) - // The launch turn's Stop lists it as `workflow`, which types it the same as the launch did. + // The launch turn's Stop lists it by the inventory's name for a workflow. expect(hookAt(records, 7).payload.background_tasks).toEqual([ expect.objectContaining({ id: 'w1kuktaid', type: 'workflow', status: 'running' }) ]) - expect(recordedTasks(server)?.get('w1kuktaid')).toEqual({ - kind: 'workflow', - launchToolUseId: 'toolu_01S3s8SyG9VLFN3QhiERnGmU' - }) const settled = row(server) expect(settled).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) expect(typeof settled.turnCompletedAt).toBe('number') From 70cc59395f0791da89b2efed191d22ce767d85dc Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:36:00 -0700 Subject: [PATCH 4/6] docs(agent-status): one-line temporary label for a teammate launch; reflow the end-row comment --- .../providers/claude-non-agent-work.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts b/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts index 2f35d468e4a5..80d2d173b6e0 100644 --- a/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts +++ b/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts @@ -90,8 +90,7 @@ export function readClaudeBackgroundTaskLaunch( if (response.status !== 'async_launched' || response.taskId === undefined) { return undefined } - // Temporary: the table calls `in_process_teammate` unknown, so a teammate launch naming a - // `taskId` (uncaptured) is recorded until the next inventory; the table fix is its own change. + // Temporary: a teammate launch (uncaptured) reads unknown until the table calls it an agent. const kind = classifyClaudeBackgroundTaskKind(response.taskType) return isAgentChildWorkKind(kind) ? undefined : { taskId: response.taskId, kind } } @@ -175,10 +174,10 @@ function readTaskNotificationField(body: string, field: string): string | undefi * task ends, even while Claude idles and sends no hook: a shell however it ended (finished, killed * with its turn's Ctrl+C or from /tasks: r1-s1, r1-s9, r3-tasks-run1), a workflow only when it * completes (r6-w1, r6-w2; one stopped or paused from /tasks writes nothing, r6-w3, r7-w6b, so it - * leaves on the next inventory). A prompt typed while Claude - * is busy writes the same row with only its text (r3-typed-run1), so the row carries no provenance - * of its own: it retires a task only when it is exactly one terminal notification naming both the - * task id and the tool call Orca recorded at launch, which Claude mints fresh for each launch. + * leaves on the next inventory). A prompt typed while Claude is busy writes the same row with + * only its text (r3-typed-run1), so the row carries no provenance of its own: it retires a task + * only when it is exactly one terminal notification naming both the task id and the tool call + * Orca recorded at launch, which Claude mints fresh for each launch. * Returns whether a task was retired. */ export function retireClaudeNonAgentTaskFromQueueRow( state: HookListenerState, From 1842e8ac31d6eabc86bced77a1b9d959da7cc19d Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 13:36:44 -0700 Subject: [PATCH 5/6] docs(agent-status): say what the Claude launch reader reads and skips; hedge W8 run 1 --- docs/reference/agent-status-store.md | 11 ++++++----- ...ground-workflow-script-wait-ctrl-c-hooks.meta.json | 2 +- .../providers/claude-non-agent-work.ts | 10 +++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/reference/agent-status-store.md b/docs/reference/agent-status-store.md index 69253a221f2f..b19d74886684 100644 --- a/docs/reference/agent-status-store.md +++ b/docs/reference/agent-status-store.md @@ -248,11 +248,12 @@ reader does not mistake them for drift: host that runs the session. A task enters it from the main agent's launching `PostToolUse`, with that call's `tool_use_id`, or from the `background_tasks` inventory Claude attaches to `Stop`, which replaces the - record. A shell's launch names it `tool_response.backgroundTaskId`; any other - kind's is Claude's `status: "async_launched"` result naming a `taskId` and a - `taskType` (a workflow's is `local_workflow`). The launch admits what the - inventory admits: every kind the shared kind table does not call an agent, - unknown kinds included, since agents belong to the subagent roster. It + record. The launch reader takes a shell's `tool_response.backgroundTaskId`; + otherwise a `status: "async_launched"` result's `taskId` and `taskType`, + captured only for workflows (`local_workflow`). It skips kinds the shared + kind table calls an agent, since agents belong to the subagent roster, and + admits unknown kinds. It skips any other result, such as a remote + `remote_launched` launch, which the next inventory still records. A task leaves on the next inventory that omits it, on a `TaskStop` result naming it, or on the `queue-operation` `enqueue` line Claude writes to its transcript the moment the task ends, read by the transcript watch below and diff --git a/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.meta.json b/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.meta.json index 2fa62d9e0a31..3db2b2c4738a 100644 --- a/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.meta.json +++ b/src/shared/__fixtures__/claude-background-workflow-script-wait-ctrl-c-hooks.meta.json @@ -6,6 +6,6 @@ "rows": 40, "recorded_at": "2026-09-30", "clock": "t is seconds since the first hook; hook records keep only the ps rows of the rig's sleep processes, taken inside the hook; a transcript line's t is the rig's poll time, so take write order from the line's own timestamp", - "note": "W8: Claude launches a two-agent background workflow whose script waits 60 s between the agents (`setTimeout`), then writes a reply; Ctrl+C 1.7 s after the launch hook, in the same turn. Claude writes its interrupt notice and sends no Stop. Agent 1's hooks follow under the cancelled turn's prompt id (the first 0.92 s after the key, past the renderer's 500 ms settle window), then 60.1 s with no hook, no main-transcript row and no sidechain write, then agent 2. Write order by Claude's own row timestamp: end row 18:43:23.636Z (completed, the launch's ids); the report turn's UserPromptSubmit hook is 41 ms later. Run 2 of two identical runs: in run 1 agent 1's first hook landed 0.487 s after the key, inside the settle window, where the renderer drops the inferred Ctrl+C and every build reads Working.", + "note": "W8: Claude launches a two-agent background workflow whose script waits 60 s between the agents (`setTimeout`), then writes a reply; Ctrl+C 1.7 s after the launch hook, in the same turn. Claude writes its interrupt notice and sends no Stop. Agent 1's hooks follow under the cancelled turn's prompt id (the first 0.92 s after the key, past the renderer's 500 ms settle window), then 60.1 s with no hook, no main-transcript row and no sidechain write, then agent 2. Write order by Claude's own row timestamp: end row 18:43:23.636Z (completed, the launch's ids); the report turn's UserPromptSubmit hook is 41 ms later. 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 settle window, where the renderer most likely drops the inferred Ctrl+C and reads Working.", "scrub": "Username was already a placeholder in the capture run. Every UUID (session, prompt, message, turn and row ids, and the ids inside transcript_path, agent_transcript_path, transcriptDir, scriptPath, and ) is replaced by a placeholder distinct to this fixture (bd); each workflow agent id by a same-shape placeholder abd…; msg_ ids by a same-length placeholder. Claude task ids, tool_use ids and wf_ run ids are verbatim. Transcript: only `queue-operation` lines and the interrupt notice row are kept (the rest, including ~56 KB prompt_snapshot attachments carrying the system prompt, are dropped)." } diff --git a/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts b/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts index 80d2d173b6e0..e3e23726ae71 100644 --- a/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts +++ b/src/shared/agent-hook-listener/providers/claude-non-agent-work.ts @@ -76,11 +76,11 @@ export function claudePaneHasLaunchRecordedTask( export type ClaudeBackgroundTaskLaunch = { taskId: unknown; kind: AgentChildWorkKind } -/** The background task a tool result launched, if any. A shell names it `backgroundTaskId`, its - * kind unknown until an inventory types it (r1-s1). Any other kind is Claude's `async_launched` - * result naming a `taskId` and a `taskType` (a workflow, r6-w1..w4), admitted as the inventory - * admits it: every kind the table does not call an agent, unknown ones included, since agents are - * the rosters' and an unknown kind fails active. */ +/** The background task a tool result launched, if any: a shell's `backgroundTaskId`, its kind + * unknown until an inventory types it (r1-s1); otherwise an `async_launched` result's `taskId` + * and `taskType` (captured for workflows, r6-w1..w4). Skips agent kinds, since agents are the + * rosters', but admits unknown ones, which fail active. Skips any other result, e.g. a remote + * `remote_launched` launch, which the next inventory still records. */ export function readClaudeBackgroundTaskLaunch( response: Record ): ClaudeBackgroundTaskLaunch | undefined { From ef679ebafb2b4c413b5e786c9f727f1e1a1c8bac Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 30 Sep 2026 13:36:52 -0700 Subject: [PATCH 6/6] test(agent-status): W5a waits on the pane's row; relayed stories reuse the shared capture helpers --- ...laude-background-workflow-captures.test.ts | 13 ++- ...er-relayed-claude-background-shell.test.ts | 88 +++++-------------- 2 files changed, 27 insertions(+), 74 deletions(-) diff --git a/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts b/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts index b82e78924e9b..0d80e67f3b06 100644 --- a/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts +++ b/src/main/agent-hooks/server-claude-background-workflow-captures.test.ts @@ -1,9 +1,9 @@ // A Claude background workflow is live work from the tool call that launches it, like a background // shell. These stories replay hook payloads recorded from Claude Code 2.1.285 over a real PTY -// (src/shared/__fixtures__/claude-background-workflow-*-hooks.jsonl, sidecars beside them). A -// workflow runs its agents one after another, so between them, and before the first, no agent -// holds the pane: after a Ctrl+C in the launch turn only the workflow's own record does. Claude -// writes a completed workflow's end as the same `queue-operation` line a shell gets. +// (src/shared/__fixtures__/claude-background-workflow-*-hooks.jsonl, sidecars beside them). The +// captured scripts run their agents one after another, so between them, and before the first, no +// agent holds the pane: after a Ctrl+C in the launch turn only the workflow's own record does. +// Claude writes a completed workflow's end as the same `queue-operation` line a shell gets. import { appendFileSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -25,7 +25,6 @@ import { replayer, row, startServer, - storedTaskFact, temporaryDir, transcriptFile, watchCaughtUp, @@ -394,8 +393,8 @@ describe('an idle-prompt Ctrl+C while a background workflow runs (captured, 2.1. await replay([9, 10, 11]) expect(row(server)).toMatchObject({ ...MONITORING, mainAgent: { state: 'done' } }) writeCaptured(records, completed, transcript) - await vi.waitFor(() => expect(storedTaskFact(server)).toBe(false), { timeout: 3_000 }) - expect(row(server)).toMatchObject({ state: 'done', mainAgent: { state: 'done' } }) + await vi.waitFor(() => expect(row(server).state).toBe('done'), { timeout: 3_000 }) + expect(row(server)).toMatchObject({ mainAgent: { state: 'done' } }) expect(row(server).turnCompletedAt).toBe(idle.turnCompletedAt) }) }) diff --git a/src/main/agent-hooks/server-relayed-claude-background-shell.test.ts b/src/main/agent-hooks/server-relayed-claude-background-shell.test.ts index 8737c27898d7..d5574265801d 100644 --- a/src/main/agent-hooks/server-relayed-claude-background-shell.test.ts +++ b/src/main/agent-hooks/server-relayed-claude-background-shell.test.ts @@ -3,19 +3,22 @@ // through a real relay-side listener, which owns the task record and reads the transcript, and // reach the desktop only as relayed rows. The relay never learns the Ctrl+C the desktop infers, so every row it // publishes afterwards still names its own working main agent. -import { appendFileSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' +import { appendFileSync } from 'node:fs' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { RelayAgentHookServer } from '../../relay/agent-hook-server' import { AgentHookServer, _internals } from './server' import { buildBody, PANE } from './server.test-fixtures' +import { cancelLabelled, hookAt, loadCapture } from './claude-cancel-capture.test-fixture' import { - cancelLabelled, - hookAt, - loadCapture, - type CapturedTranscriptScan -} from './claude-cancel-capture.test-fixture' + captureEpoch, + cleanUpCaptureReplays, + pressCtrlC, + queueLine, + row, + temporaryDir, + transcriptFile, + writeCaptured +} from './claude-background-task-capture.test-fixture' const { getCohortAtEmitMock, trackMock } = vi.hoisted(() => ({ getCohortAtEmitMock: vi.fn(), @@ -25,7 +28,6 @@ const { getCohortAtEmitMock, trackMock } = vi.hoisted(() => ({ vi.mock('../telemetry/client', () => ({ track: trackMock })) vi.mock('../telemetry/cohort-classifier', () => ({ getCohortAtEmit: getCohortAtEmitMock })) -const temporaryPaths: string[] = [] const running: { stop: () => void }[] = [] beforeEach(() => { @@ -41,19 +43,11 @@ afterEach(() => { for (const server of running.splice(0)) { server.stop() } - for (const path of temporaryPaths.splice(0)) { - rmSync(path, { recursive: true, force: true }) - } + cleanUpCaptureReplays() vi.useRealTimers() vi.restoreAllMocks() }) -function temporaryDir(prefix: string): string { - const path = mkdtempSync(join(tmpdir(), prefix)) - temporaryPaths.push(path) - return path -} - type SshPane = { relay: RelayAgentHookServer desktop: AgentHookServer @@ -63,7 +57,7 @@ type SshPane = { async function startSshPane(): Promise { const desktop = new AgentHookServer() const relay = new RelayAgentHookServer({ - endpointDir: temporaryDir('orca-relayed-shell-'), + endpointDir: temporaryDir(), token: 'relayed-shell-token', forward: (envelope) => desktop.ingestRemote(envelope, 'conn-1') }) @@ -84,39 +78,11 @@ async function startSshPane(): Promise { } } -function row(server: AgentHookServer) { - const entry = server.getStatusSnapshotForPane(PANE)[0] - if (!entry) { - throw new Error('the pane has no row') - } - return entry -} - -function pressCtrlC(server: AgentHookServer): boolean { - const baseline = row(server) - return server.inferInterrupt({ - paneKey: PANE, - baselineUpdatedAt: baseline.receivedAt, - baselineStateStartedAt: baseline.stateStartedAt, - baselinePrompt: baseline.prompt, - baselineAgentType: 'claude', - intent: 'ctrl-c' - }) -} - describe('a relayed Ctrl+C in the turn that launched a background shell (captured, 2.1.284)', () => { const records = loadCapture('claude-background-shell-ctrl-c-hooks') const cancel = cancelLabelled(records, 'S1-ctrl-c-mid-essay') - const scan = records.find( - (record): record is CapturedTranscriptScan => - record.kind === 'transcript' && record.label === 'queue-operation-enqueue' - ) - if (!scan) { - throw new Error('the capture has no enqueue line') - } - // JSON.parse returns any; the timestamp is checked by Date.parse. - const parsed: Record = JSON.parse(scan.lines[0]) - const t0 = Date.parse(String(parsed.timestamp)) - scan.t * 1000 + const scan = queueLine(records, 'queue-operation-enqueue') + const t0 = captureEpoch(records) async function launchAndCancel(pane: SshPane, transcript: string): Promise { for (const index of [0, 1, 2, 3, 4, 5, 6]) { @@ -129,8 +95,7 @@ describe('a relayed Ctrl+C in the turn that launched a background shell (capture it('folds the desktop-inferred cancel to monitoring from the relay-published launch', async () => { const pane = await startSshPane() - const transcript = join(temporaryDir('orca-relayed-shell-transcript-'), 'session.jsonl') - writeFileSync(transcript, '') + const transcript = transcriptFile() await launchAndCancel(pane, transcript) expect(row(pane.desktop)).toMatchObject({ state: 'working', @@ -143,8 +108,7 @@ describe('a relayed Ctrl+C in the turn that launched a background shell (capture it('settles done and interrupted when the relay reads the end line long after the latch window', async () => { const pane = await startSshPane() - const transcript = join(temporaryDir('orca-relayed-shell-transcript-'), 'session.jsonl') - writeFileSync(transcript, '') + const transcript = transcriptFile() await launchAndCancel(pane, transcript) expect(row(pane.desktop)).toMatchObject({ state: 'working', workingMode: 'monitoring' }) // Hand-placed timing: the shell ends 20 s after the Ctrl+C with no hook (as a /tasks kill @@ -169,16 +133,8 @@ describe('a relayed Ctrl+C in the turn that launched a background shell (capture describe('a relayed Ctrl+C in the turn that launched a background workflow (captured, 2.1.285)', () => { const records = loadCapture('claude-background-workflow-ctrl-c-hooks') const cancel = cancelLabelled(records, 'W2-ctrl-c-mid-essay') - const scan = records.find( - (record): record is CapturedTranscriptScan => - record.kind === 'transcript' && record.label === 'queue-operation-enqueue' - ) - if (!scan) { - throw new Error('the capture has no enqueue line') - } - // JSON.parse returns any; the timestamp is checked by Date.parse. - const parsed: Record = JSON.parse(scan.lines[0]) - const t0 = Date.parse(String(parsed.timestamp)) - scan.t * 1000 + const scan = queueLine(records, 'queue-operation-enqueue') + const t0 = captureEpoch(records) async function post(pane: SshPane, transcript: string, indices: number[]): Promise { for (const index of indices) { @@ -189,8 +145,7 @@ describe('a relayed Ctrl+C in the turn that launched a background workflow (capt it('monitors the workflow after its agent stops, then settles on the end line the relay reads', async () => { const pane = await startSshPane() - const transcript = join(temporaryDir('orca-relayed-workflow-transcript-'), 'session.jsonl') - writeFileSync(transcript, '') + const transcript = transcriptFile() await post(pane, transcript, [0, 1, 2, 3, 4, 5, 6, 7, 8]) vi.setSystemTime(t0 + cancel.t * 1000) expect(pressCtrlC(pane.desktop)).toBe(true) @@ -203,8 +158,7 @@ describe('a relayed Ctrl+C in the turn that launched a background workflow (capt mainAgent: { state: 'done', outcome: 'cancellation' } }) - vi.setSystemTime(t0 + scan.t * 1000) - appendFileSync(transcript, `${scan.lines[0]}\n`) + writeCaptured(records, scan, transcript) await vi.waitFor(() => expect(row(pane.desktop).workingMode).toBeUndefined(), { timeout: 3_000 })