Skip to content

Name the spawning call on a Codex subagent's start - #1939

Open
realtonyyoung wants to merge 1 commit into
mainfrom
tonyyoung/codex-subagent-spawn-call
Open

realtonyyoung wants to merge 1 commit into
mainfrom
tonyyoung/codex-subagent-spawn-call

Conversation

@realtonyyoung

@realtonyyoung realtonyyoung commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Part of AI-3070 (no GitHub issue)

What & why

A Codex import posts every subagent's start after the whole parent transcript, so the server puts every Codex agent row under the last turn. The start now carries spawn_call_id: the parent's spawn_agent call whose output names the agent's agent_path as its task_name. A grandchild is imported onto the root, so it names its nearest root-level ancestor's call. The live watcher sends the same field, read once per scan that finds a new child

The server half, which stores the call on the start and places the row by it, is kurrent-io/kcap-server#2673. A server without it ignores the field

Where to look

ReadSpawnCalls reads the whole parent rollout once per import. A spawn whose output is not written yet names no call, and the start keeps its own position

Verification

  • An_imported_subagent_start_names_the_call_that_spawned_it_or_its_ancestor drives SessionImporter.ImportSessionAsync over a rollout tree with a child, a grandchild, an agent whose path only another tool's output carries, and a truncated last line. Red when the importer sends no call, and when a non-spawn output is accepted: Expected to be equivalent to [[019fec44…, call_spawn], …]
  • A_path_takes_its_own_spawning_call_or_its_nearest_ancestors is red on /root/a/b/c when the walk stops at the exact path
  • The call ids and task_name outputs match real Codex 0.146 rollouts

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Session imports and live monitoring now associate subagents with the action that spawned them, when a match is available.
    • When a subagent has no direct match, the nearest matching parent action is used. Subagents without a match remain unlinked.

An import posts every start after the parent transcript, so the server can place a subagent's row only by the call; a grandchild names its root-level ancestor's call, since it is imported onto the root

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 25, 2026

Copy link
Copy Markdown

AI-3070

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 16236772-75e8-4113-80d5-d6b4e1f50cd8
Commits

Reviewing files that changed from the base of the PR and between b9e9b92 and 56856a6.

Files selected for processing: 4
  • src/Capacitor.Cli.Core/Harness/Codex/CodexSubagentDiscovery.cs
  • src/Capacitor.Cli/Commands/SessionImporter.cs
  • src/Capacitor.Cli/Commands/WatchCommand.cs
  • test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

Walkthrough

Codex subagent-start payloads now include the spawn_call_id associated with an agent path when the parent transcript contains a matching spawn_agent call. Import and watch flows resolve direct or nearest-ancestor matches; payloads omit the field when no match exists.

Changes: Codex spawn-call association

Layer / File(s) Summary
Spawn-call discovery and payload
src/Capacitor.Cli.Core/Harness/Codex/CodexSubagentDiscovery.cs
The helper maps task_name values from parent-rollout spawn calls to call IDs. It resolves an agent path to its own or nearest ancestor’s call ID, and adds spawn_call_id to the payload when a value is available.
Import and watch registration
src/Capacitor.Cli/Commands/SessionImporter.cs, src/Capacitor.Cli/Commands/WatchCommand.cs, test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs
The import and watch flows pass resolved call IDs when registering subagents. Tests check exact and ancestor path matching, unmatched paths, and the resulting import payloads.

Priority: Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant SessionImporter
  participant WatchCommand
  participant CodexSubagentDiscovery
  participant ParentRollout
  participant SubagentStartEndpoint
  SessionImporter->>CodexSubagentDiscovery: ReadSpawnCalls for parent rollout
  CodexSubagentDiscovery->>ParentRollout: Scan spawn_agent calls and outputs
  ParentRollout-->>CodexSubagentDiscovery: Return task names and call IDs
  CodexSubagentDiscovery-->>SessionImporter: Return spawn-call mapping
  SessionImporter->>CodexSubagentDiscovery: Resolve call for agent path
  SessionImporter->>SubagentStartEndpoint: Send subagent-start payload
  WatchCommand->>CodexSubagentDiscovery: Read and resolve spawn calls on demand
  CodexSubagentDiscovery->>ParentRollout: Scan spawn_agent calls and outputs
  CodexSubagentDiscovery-->>WatchCommand: Return matching call ID
  WatchCommand->>SubagentStartEndpoint: Send subagent-start payload
Loading

Suggested reviewers: inok

Merge Risk: Minimal · up to 56856

The spawn-call association is ready to merge after normal checks; no actionable risk remains.

Security architecture risk: Low · up to 56856

The change preserves existing client-side child identity and registration safeguards. No introduced security vulnerability was established, but server-side ownership validation and compatibility with older deployments could not be confirmed.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated new exposure is a transcript-derived string crossing the existing lifecycle-hook boundary for imported or watched Codex children. The inspected client changes do not grant tool execution, credentials, or a new destination. Effective downstream scope cannot be bounded without the receiver's session and tenant checks.

Trust Boundaries and Controls

  • observed — Transcript content influences the outbound call reference, but unrelated tool outputs are excluded unless their call ID was previously recorded for spawn_agent. JSON shape guards reject wrong-typed fields, malformed JSON is skipped, and I/O or access failures produce no mapping. These parsing controls do not authenticate transcript contents or establish authorization for the referenced server object.

Resilience and Maintainability Implications

  • observed — Existing failure containment remains: live watching starts child streaming only after successful registration and removes failed registrations from the seen set for retry. Import likewise skips content after failed registration and withholds stop after transcript delivery failure. The new field does not alter these transition gates.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: adding the spawning call name to Codex subagent start events.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@realtonyyoung
realtonyyoung marked this pull request as ready for review October 25, 2026 02:44
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Place Codex subagents at their spawning calls

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Identify each Codex subagent’s parent spawn_agent call so its row appears on the correct turn.
• Use the nearest root-level ancestor’s call for grandchildren imported onto the root session.
• Send the call ID from imports and live watching, with tests for missing and unrelated outputs.
Diagram

graph TD
  A["Parent rollout"] --> B["Spawn call map"] --> C["Session importer"] --> E["Start hook"] --> F["Server placement"]
  B --> D["Live watcher"] --> E
Loading
High-Level Assessment

The optional call ID is a focused fit for the existing start-hook contract: it preserves starts without a known spawn output and lets the server place rows independently of delivery order. Interleaving starts during transcript import was considered, but Codex rollouts lack the markers needed to do that reliably.

Files changed (4) +147 / -7

Bug fix (3) +86 / -7
CodexSubagentDiscovery.csResolve spawning calls and add them to start payloads +75/-3

Resolve spawning calls and add them to start payloads

• Scans the parent rollout for 'spawn_agent' calls whose outputs name an agent path, ignoring incomplete lines and unrelated tool outputs. Resolves an exact path or its nearest mapped ancestor and conditionally adds 'spawn_call_id' to the start payload.

src/Capacitor.Cli.Core/Harness/Codex/CodexSubagentDiscovery.cs

SessionImporter.csName spawning calls on imported subagent starts +4/-1

Name spawning calls on imported subagent starts

• Reads the parent’s spawn calls once per Codex import and includes the resolved call ID when posting each descendant’s start.

src/Capacitor.Cli/Commands/SessionImporter.cs

WatchCommand.csName spawning calls on live subagent starts +7/-3

Name spawning calls on live subagent starts

• Lazily reads spawn calls once per scan that encounters a new child and passes the resolved ID through the live start hook.

src/Capacitor.Cli/Commands/WatchCommand.cs

Tests (1) +61 / -0
CodexSubagentDiscoveryTests.csTest imported call IDs and ancestor lookup +61/-0

Test imported call IDs and ancestor lookup

• Verifies start-hook payloads for a child, a flattened grandchild, and an agent named only by an unrelated tool output, with a truncated rollout line. Parameterized cases check exact matches, ancestor fallback, and paths that must not match.

test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (3) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. JSON type checks bypass shared helpers 📘 Rule violation ⚙ Maintainability
Description
TaskNameOf compares JsonElement.ValueKind directly for string and object detection instead of
using the available JsonElementExtensions helpers. Any future type-checking change must be
repeated in these branches, leaving the new Codex rollout parsing path inconsistent with the shared
JSON conventions.
Code

src/Capacitor.Cli.Core/Harness/Codex/CodexSubagentDiscovery.cs[R307-308]

+        if (output.TryGetProperty("output", out var body) && body.ValueKind == JsonValueKind.String) {
+            try {
Relevance

●●● Strong

Recent accepted precedents require shared JSON helpers for direct ValueKind comparisons.

PR-#1758
PR-#1751
PR-#1726

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed TaskNameOf method directly compares body.ValueKind and doc.RootElement.ValueKind
instead of using extension helpers, which violates the checklist requirement for changed C# JSON
code.

Rule 2270023: Use JsonElementExtensions helpers instead of direct JsonValueKind comparisons
src/Capacitor.Cli.Core/Harness/Codex/CodexSubagentDiscovery.cs[306-317]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `TaskNameOf` implementation directly compares `JsonElement.ValueKind`, contrary to the shared JSON helper convention.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Codex/CodexSubagentDiscovery.cs[307-311]

## Recommended Fix
Replace the direct string and object `ValueKind` comparisons with the corresponding `JsonElementExtensions` methods, such as `IsString()` and `IsObject()`, while preserving the existing parsing behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


2. Test writes temp files outside helpers 📘 Rule violation ⚙ Maintainability
Description
The new test appends directly with File.AppendAllLines(parent, ...) after deriving parent from
tmp.Path, rather than using the TempDir file helpers for state under the temporary root. This
bypasses the helper's path and file-operation abstraction in the new filesystem-writing path, even
though disposal still occurs at the end of the test.
Code

test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs[R263-268]

+        var day    = DayDir(tmp.Path);
+        var parent = WriteParent(day);
+        File.AppendAllLines(parent, [
+            Item("""{"type":"function_call","name":"spawn_agent","namespace":"collaboration","call_id":"call_spawn","arguments":"{\"task_name\":\"spec_quality\"}"}"""),
+            Item("""{"type":"function_call_output","call_id":"call_spawn","output":"{\"task_name\":\"/root/spec_quality\"}"}"""),
+            Item("""{"type":"function_call","name":"exec_command","call_id":"call_exec","arguments":"{}"}"""),
Relevance

●●● Strong

Recent accepted precedents require TempDir-managed paths and helpers for temporary filesystem
writes.

PR-#1318
PR-#1758
PR-#1676

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added test derives a directory from tmp.Path and directly appends rollout content with
File.AppendAllLines; the checklist requires temporary filesystem state to use TempDir members
for paths and file operations.

Rule 2767472: Use TempDir helper for all temporary filesystem state in tests
test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs[263-271]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new test performs direct filesystem writes under `tmp.Path` instead of using `TempDir` path and file helpers.

## Fix Focus Areas
- test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs[263-271]

## Recommended Fix
Construct the rollout paths with `TempDir.PathTo` or `CreateDir`, and create or update the parent rollout through `TempDir.CreateFile` or another helper-backed operation rather than calling `File.AppendAllLines` directly under the temporary root.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗



Informational

3. New test manually owns temporary state 📘 Rule violation ▣ Testability
Description
The new import test creates a TempDir local with new TempDir() instead of receiving the
framework-managed directory through a public [TempDir] property. The test class is instantiated by
TUnit, so this local ownership bypasses the required lifecycle injection for the added test.
Code

test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs[R257-260]

+    [Test]
+    public async Task An_imported_subagent_start_names_the_call_that_spawned_it_or_its_ancestor() {
+        using var tmp    = new TempDir();
+        using var server = WireMockServer.Start();
Relevance

● Weak

Recent rejection explicitly declined replacing local TempDir construction with injected
framework-managed properties.

PR-#1865
PR-#1596

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added test method creates a disposable TempDir local, while the checklist requires TUnit test
classes to use a public required [TempDir] property and avoid manually managed temporary-directory
instances.

Rule 2808173: Use injected [TempDir] public required property in test classes instead of manual fields
test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs[257-260]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new TUnit test manually creates and disposes a `TempDir` instead of using framework-managed `[TempDir]` injection.

## Fix Focus Areas
- test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs[257-260]

## Recommended Fix
Add a single public required `[TempDir]` property to the test class and replace the test's local `new TempDir()` with that injected property, using its path after injection and leaving disposal to TUnit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


Grey Divider

Context sources
✅ Compliance rules (platform): 59 rules
✅ Cross-repo context — repo relationships
  Explored: repo: kurrent-io/kcap-server (branch: tonyyoung/ai-3070-codex-subagent-spawn-link, sha: df74ff1c) — View relationship
Review mode: Auto: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 9/18, lines 154/200; both must reach the floor). Router rationale: Multiple parsing, ancestry, import, and live-watch paths create subtle defect opportunities.

Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +307 to +308
if (output.TryGetProperty("output", out var body) && body.ValueKind == JsonValueKind.String) {
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Json type checks bypass shared helpers 📘 Rule violation ⚙ Maintainability

TaskNameOf compares JsonElement.ValueKind directly for string and object detection instead of
using the available JsonElementExtensions helpers. Any future type-checking change must be
repeated in these branches, leaving the new Codex rollout parsing path inconsistent with the shared
JSON conventions.
Agent Prompt
## Issue description
The new `TaskNameOf` implementation directly compares `JsonElement.ValueKind`, contrary to the shared JSON helper convention.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Harness/Codex/CodexSubagentDiscovery.cs[307-311]

## Recommended Fix
Replace the direct string and object `ValueKind` comparisons with the corresponding `JsonElementExtensions` methods, such as `IsString()` and `IsObject()`, while preserving the existing parsing behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment on lines +263 to +268
var day = DayDir(tmp.Path);
var parent = WriteParent(day);
File.AppendAllLines(parent, [
Item("""{"type":"function_call","name":"spawn_agent","namespace":"collaboration","call_id":"call_spawn","arguments":"{\"task_name\":\"spec_quality\"}"}"""),
Item("""{"type":"function_call_output","call_id":"call_spawn","output":"{\"task_name\":\"/root/spec_quality\"}"}"""),
Item("""{"type":"function_call","name":"exec_command","call_id":"call_exec","arguments":"{}"}"""),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Test writes temp files outside helpers 📘 Rule violation ⚙ Maintainability

The new test appends directly with File.AppendAllLines(parent, ...) after deriving parent from
tmp.Path, rather than using the TempDir file helpers for state under the temporary root. This
bypasses the helper's path and file-operation abstraction in the new filesystem-writing path, even
though disposal still occurs at the end of the test.
Agent Prompt
## Issue description
The new test performs direct filesystem writes under `tmp.Path` instead of using `TempDir` path and file helpers.

## Fix Focus Areas
- test/Capacitor.Cli.Tests.Unit/Harness/Codex/CodexSubagentDiscoveryTests.cs[263-271]

## Recommended Fix
Construct the rollout paths with `TempDir.PathTo` or `CreateDir`, and create or update the parent rollout through `TempDir.CreateFile` or another helper-backed operation rather than calling `File.AppendAllLines` directly under the temporary root.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant