Skip to content

Record agents spawned by Claude Code's Workflow tool - #2687

Open
peyton-alt wants to merge 3 commits into
mainfrom
fix/claude-code-workflow-subagents
Open

peyton-alt wants to merge 3 commits into
mainfrom
fix/claude-code-workflow-subagents

Conversation

@peyton-alt

@peyton-alt peyton-alt commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

https://entire.io/gh/entireio/cli/trails/1505

Summary

Agents that Claude Code's Workflow tool launches left no trace in the checkpoint: no tasks/ entry, no stored transcript, no subagent tokens. Entire's launch hooks (PreToolUse/PostToolUse) match only the Agent tool, so a workflow agent never got an in-flight record, and each of its SubagentStop events was skipped as "no live in-flight marker".

Claude Code fires SubagentStart for every workflow agent with agent_id and agent_type: "workflow-subagent". This PR:

  • installs Claude Code's SubagentStart hook with matcher workflow-subagent, so direct Agent launches never invoke it (the parser also ignores any other agent type);
  • records a live task record per workflow agent, keyed by its agent_id (a Workflow call's tool_use_id is shared by all its agents); a repeated start cannot reset a record still in session state;
  • lets the existing SubagentStop correlation by agent_id complete it from agent_transcript_path under subagents/workflows/<run>/;
  • makes the transcript fallbacks (a commit while an agent is still running, the SessionEnd sweep) also look in the run directory, through one shared resolver in paths that replaces two duplicates;
  • counts workflow agents in the session's subagent_tokens, for the runs the parent transcript launched (the launch's structured toolUseResult.runId, with the result's Run ID: wf_… text as a fallback), so entire import puts their tokens on the launching turn; an agent found in more than one run counts once, from its newest transcript;
  • logs agent_id/agent_type when a SubagentStop is skipped.

Existing installs get the new hook with entire enable; until then entire status and entire doctor report the Claude hook config as outdated.

Part of #2685: this PR gives each workflow agent a task record and counts them in the session's subagent tokens. Each record's own token_usage is still undercounted (see below), which is being fixed in a separate PR; #2685 should close once both are in.

Verified

  • Live, Claude Code 2.1.291, binary from this branch: a Workflow running three parallel agents (each writing its own file) produced tasks/<agent_id>/ ×3 with task.json, the agent transcript and the right file; checkpoint explain --json lists 3 tasks. Session subagent_tokens equals the summed message.usage of the three agent transcripts before and after the commit, with no double counting in a later checkpoint. A direct Agent subagent in the same session still gets exactly one toolu_…-keyed record, and Entire's subagent-start did not run for it. No warnings in entire.log.
  • Same reproduction on main: no tasks/, "tasks": [], no subagent_tokens, three skipped SubagentStops.
  • mise run check passes. New tests (hook install/upgrade/uninstall/health, parse filter, repeated start, resolver layout, token discovery and dedup, import turn split, two integration tests including a commit while the agent is still running) fail on main.

Not in this PR

  • Per-task token_usage undercounts: it is computed at SubagentStop, before Claude Code has written the agent's final message to its transcript, so a task record misses its last API call(s) while the stored transcript has them (live: task.json 1 call, stored transcript 2). The same race exists on main for direct Agent subagents. Session subagent_tokens is computed later and is correct. Fix in a separate PR off main (recompute each task's tokens from the transcript stored at checkpoint time).
  • A task record's files come from the agent's transcript, so files an agent changes through Bash (echo >>) are not listed on its record; they still appear in the session's files_touched. Same as direct Agent subagents.
  • Claude Code Workflow-spawned agents leave no task record: only the Agent tool is matched, and SubagentStop is skipped #2685 also reports a fourth, late SubagentStop for three agents; two live runs here produced exactly three. The skip log now includes agent_id and agent_type, so a repeat can be identified.
  • Line attribution of subagent edits to the human: Background subagent's lines are attributed to the human when the parent commits in the same turn #2653.
  • session adopt does not yet accept a workflow agent's transcript path.
  • The web task view still needs checking with task ids that are agent ids rather than parent tool_use_ids.

🤖 Generated with Claude Code

@peyton-alt
peyton-alt requested a review from a team as a code owner October 6, 2026 21:33
Copilot AI balanced review requested due to automatic review settings October 6, 2026 21:33

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit af049a2. Configure here.

_, err := os.Stat(path)
return err == nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Workflow files missed at turn end

Medium Severity

ExtractAllModifiedFiles still discovers children only via ExtractSpawnedAgentIDs and the direct agent-<id>.jsonl layout. CalculateTotalTokenUsage in the same file now uses subagentTranscriptPaths so workflow runs named by Run ID: are included, but turn-end file extraction never opens subagents/workflows/<runId>/. Workflow edits that appear only in those transcripts are omitted from the parent turn's transcript-derived file list.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit af049a2. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Token discovery can misattribute usage, a fixture fails on Windows, and transcript lookup repeats full directory scans.

Review effort: Balanced
Findings: 2 High severity · 1 Medium severity

Open (3)
What changed in this PR

Adds Claude Code Workflow-agent capture to Entire’s checkpoint pipeline, addressing missing task records, transcripts, and session token totals in #2685.

Changes:

  • Registers Workflow-specific launch hooks and idempotent task records.
  • Shares transcript resolution and includes launched Workflow runs in token accounting.
  • Adds regression coverage and updates lifecycle documentation.
File Description
docs/​development/​filesystem-safety.md Documents Workflow directory reads.
docs/​architecture/​sessions-and-checkpoints.md Explains Workflow task lifecycle.
docs/​architecture/​agent-integration-checklist.md Adds Workflow launch guidance.
docs/​architecture/​agent-guide.md Updates event contracts.
cmd/​entire/​cli/​validation/​validators.go Validates Workflow run IDs.
cmd/​entire/​cli/​validation/​validators_test.go Tests run-ID validation.
cmd/​entire/​cli/​transcript.go Uses shared transcript resolver.
cmd/​entire/​cli/​transcript_test.go Tests Workflow transcript lookup.
cmd/​entire/​cli/​strategy/​manual_commit_condensation.go Shares condensation transcript lookup.
cmd/​entire/​cli/​strategy/​manual_commit_condensation_test.go Tests Workflow fallback resolution.
cmd/​entire/​cli/​session/​state.go Documents agent-keyed task records.
cmd/​entire/​cli/​paths/​subagent_transcript.go Adds shared transcript discovery.
cmd/​entire/​cli/​paths/​subagent_transcript_test.go Tests layouts and discovery restrictions.
cmd/​entire/​cli/​lifecycle.go Preserves repeated launches and improves diagnostics.
cmd/​entire/​cli/​lifecycle_test.go Tests repeated-launch preservation.
cmd/​entire/​cli/​integration_test/​subagent_workflow_test.go Covers Workflow capture and mid-run commits.
cmd/​entire/​cli/​integration_test/​hooks.go Adds Workflow hook simulation.
cmd/​entire/​cli/​integration_test/​agent_test.go Updates installed-hook expectations.
cmd/​entire/​cli/​hook_registry.go Classifies the new subagent hook.
cmd/​entire/​cli/​agentimport/​claude_test.go Tests launching-turn token attribution.
cmd/​entire/​cli/​agent/​event.go Adds idempotent-launch flag.
cmd/​entire/​cli/​agent/​claudecode/​types.go Adds Workflow hook payload types.
cmd/​entire/​cli/​agent/​claudecode/​transcript.go Discovers Workflow runs and aggregates tokens.
cmd/​entire/​cli/​agent/​claudecode/​transcript_test.go Tests token discovery and deduplication.
cmd/​entire/​cli/​agent/​claudecode/​lifecycle.go Parses Workflow launch events.
cmd/​entire/​cli/​agent/​claudecode/​lifecycle_test.go Tests launch parsing and filtering.
cmd/​entire/​cli/​agent/​claudecode/​hooks.go Installs, removes, and checks the hook.
cmd/​entire/​cli/​agent/​claudecode/​hooks_test.go Tests hook configuration lifecycle.
.claude/​settings.json Enables the Workflow launch hook.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +162 to +171
func ExtractWorkflowRunIDs(transcript []TranscriptLine) []string {
var runIDs []string
forEachToolResultText(transcript, func(_, text string) {
if runID := extractIDAfter(text, "Run ID: ", true); runID != "" &&
validation.ValidateWorkflowRunID(runID) == nil && !slices.Contains(runIDs, runID) {
runIDs = append(runIDs, runID)
}
})
return runIDs
}
Comment on lines +111 to +114
writeWorkflowFixture(t, filepath.Join(SubagentsDir(dir, sessionID), SubagentWorkflowsDirName, "wf_1", "agent-*.jsonl"))
assert.Empty(t, ResolveSubagentTranscriptPath(dir, sessionID, ""))
assert.Empty(t, ResolveSubagentTranscriptPath(dir, sessionID, "*"))
assert.Empty(t, ResolveSubagentTranscriptPath(dir, sessionID, "../x"))
if legacy := filepath.Join(transcriptDir, name); pathExists(legacy) {
return legacy
}
return WorkflowAgentTranscripts(subagentsDir)[agentID]
Agents a Workflow tool call launches never reached Entire's launch
hooks, which match only the Agent tool, so each SubagentStop found no
in-flight record and was skipped: no task record, no transcript, no
subagent tokens.

Claude Code fires SubagentStart for each workflow agent with its
agent_id and agent_type "workflow-subagent". Entire now installs that
hook (matcher "workflow-subagent") and records a live task record keyed
by the agent id; a repeated start does not reset it. SubagentStop then
completes it from the agent's transcript under
subagents/workflows/<run>/. Transcript fallbacks (commit while an agent
runs, the SessionEnd sweep) also look in the run directory, and the
session's subagent token total counts the agents of workflow runs the
parent transcript launched.

Existing installs need `entire enable` to add the hook; `entire status`
and `entire doctor` report the hook config as outdated until then.

Part of #2685 (per-task token usage is fixed separately).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M49HRP4R4KHH7S665NYK4Y7W
@peyton-alt
peyton-alt force-pushed the fix/claude-code-workflow-subagents branch from af049a2 to 9d505c2 Compare October 7, 2026 00:25
peyton-alt and others added 2 commits October 6, 2026 20:15
Session subagent tokens found Workflow runs only from the result text's
"Run ID:" wording, and only the first one per result. They now also
read the launch's structured toolUseResult.runId (taskType
local_workflow), so a change in Claude Code's prose cannot silently
drop workflow agents' tokens, and every "Run ID:" in a result counts.

Also documents why turn-end file extraction does not look up workflow
agents (they run after the parent's turn ends; their files reach the
session through their task records) and that a Workflow launched from
inside a subagent is not counted in the parent's subagent_tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M4A5R5RF266YDVKTBWHTXY7V
…ranscript

An agent ID found in two runs (a resumed run can carry an agent) took
whichever run was read last, in both the by-ID transcript lookup and the
session subagent token count. Both now keep the most recently modified
transcript, so the result does not depend on read order and the agent
is counted once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M4A6HQTHQV74Y0TW000AHCJ4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants