Skip to content

[AI-1839] Capture Codex collab subagents: child watchers, parent-aware import, locator guard - #515

Merged
alexeyzimarev merged 2 commits into
mainfrom
alexeyzimarev/ai-1839-codex-collab-subagent-capture
Aug 10, 2026
Merged

[AI-1839] Capture Codex collab subagents: child watchers, parent-aware import, locator guard#515
alexeyzimarev merged 2 commits into
mainfrom
alexeyzimarev/ai-1839-codex-collab-subagent-capture

Conversation

@alexeyzimarev

Copy link
Copy Markdown
Member

Closes the CLI half of AI-1839 — Codex CLI 0.146+ (multi_agent_version: v2) forks every spawn_agent collab subagent into its own rollout under the shared ~/.codex/sessions tree, and kcap captured none of it: children were never watched live, kcap import --codex would land them as unrelated top-level sessions, and the daemon's rollout locator could mis-link a hosted session to one of its own children (children inherit the parent's cwd and are created moments later).

What's here

  • CodexSubagentDiscovery (Core, new) — shared discovery over the child session_meta linkage (thread_source: "subagent" + parent_thread_id), used by the live scan, the teardown and the import walk. Pins the trap that a child's session_meta.session_id holds the parent's id (its own id is only in id/the filename) — nothing may ever key a child by session_id.
  • Live captureWatchCommand.ScanCodexSubagents, the codex arm of the parent watcher's subagent scan (mirrors Gemini/OpenCode): fail-closed subagent-start, then a detached child watcher streaming the child rollout under its dashless thread id (→ AgentSubsession-*). Disk enumeration (not the in-band sub_agent_activity events) so a restarted parent watcher still recovers already-spawned children; definitive non-children are cached per-file, a mid-write header is retried, a later-day-dir child (midnight rollover) is found.
  • TeardownCodexSubagentTeardown (mirrors GeminiSubagentTeardown): on the parent's session-end synthesis (which for codex covers idle_timeout and parent-exit — there is no session-end hook), kill each child watcher, drain its tail, POST subagent-stop, all before SessionEnded.
  • Import — subagent rollouts are excluded from top-level discovery; SessionImporter.ImportSessionAsync's codex arm imports every transitive descendant as a direct subagent of the root (flat AgentSubsession model, like the Gemini import), with vendor: "codex" stamped on child batches.
  • Locator guardCodexSessionRolloutLocator.MatchRollout returns a definitive No for any subagent rollout, even on a cwd match.

Testing

  • New CodexSubagentDiscoveryTests (12) pin the linkage parsing (incl. the parent-id trap), rule-out caching vs mid-write retry, midnight-rollover discovery, descendant flattening, and agent-type derivation; 2 new CodexSessionRolloutLocatorTests pin the guard. All pass.
  • Full Capacitor.Cli.Tests.Unit run: the only failures are the known AgentOrchestratorVendorTests-family PTY timing flakes, which fail identically on an untouched main checkout under load and pass individually in isolation (verified both).

Residual

A parent session already fully imported/recorded before this change classifies AlreadyImported on re-import, so its children are not retroactively attached — capture is forward-looking. The server half (normalizer support for agent_message handoff prose + subagent session_meta suppression) is the kcap-server PR on the same issue; this PR works against existing servers (the subagent-start/stop + agent-batch wire is vendor-neutral and long shipped).

🤖 Generated with Claude Code

…e import, locator guard

Codex CLI 0.146+ (multi_agent_version v2) forks every spawn_agent subagent
into its OWN rollout under the shared ~/.codex/sessions tree, linked back via
the child session_meta's parent_thread_id / thread_source:"subagent". kcap
had zero handling: children were never watched live, import would land them
as unrelated top-level sessions, and the daemon's rollout locator could
mis-link a hosted session to one of its own children (same cwd, newer
timestamp).

- CodexSubagentDiscovery (Core): shared header-linkage discovery for the
  watcher scan, the teardown and the import walk. Pins the trap that a child
  session_meta's `session_id` holds the PARENT's id (own id only in `id`).
- WatchCommand.ScanCodexSubagents: codex arm of the parent watcher's live
  subagent scan (mirrors Gemini/OpenCode) — fail-closed subagent-start, then
  a detached child watcher streaming the child rollout under its dashless
  thread id (AgentSubsession-*). Disk enumeration, so a restarted parent
  still recovers already-spawned children; definitive non-children cached,
  mid-write headers retried.
- CodexSubagentTeardown: parent session-end synthesis (idle_timeout AND
  parent-exit — codex has no session-end hook) kills each child watcher,
  drains its tail and posts subagent-stop, before SessionEnded.
- Import: subagent rollouts excluded from top-level discovery; the codex arm
  of SessionImporter.ImportSessionAsync imports every transitive descendant
  as a direct subagent of the root (flat AgentSubsession model, like Gemini),
  with vendor:"codex" stamped on child batches.
- CodexSessionRolloutLocator.MatchRollout: a subagent rollout is a definitive
  non-match even when its cwd matches.

Known residual: a parent already fully imported/recorded before this change
is classified AlreadyImported on re-import, so its children are not
retroactively attached; capture is forward-looking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown

AI-1839

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Capture Codex collab subagents via shared discovery, import nesting, and locator guard

🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add shared Codex subagent rollout discovery based on session_meta parent linkage.
• Live-watch Codex child rollouts, finalize them on parent end, and import descendants nested.
• Prevent hosted-session rollout locator from ever matching subagent rollouts by cwd.
Diagram

graph TD
  W["WatchCommand"] --> D["CodexSubagentDiscovery"] --> FS[("~/.codex/sessions")]
  W --> WM["WatcherManager"] --> HK{{"/hooks subagent-*"}}
  T["CodexSubagentTeardown"] --> D --> FS
  T --> WM --> HK
  I["SessionImporter"] --> D --> FS
  L["CodexSessionRolloutLocator"] -->|"guard: reject subagents"| FS
  subgraph Legend
    direction LR
    _svc["Component/Service"] ~~~ _fs[("Filesystem")] ~~~ _ext{{"HTTP endpoint"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Rely on in-band Codex sub_agent_activity events
  • ➕ Avoids filesystem scanning across shared sessions tree
  • ➕ Lower per-tick overhead if events are reliable and complete
  • ➖ Breaks recovery after watcher restart/crash (events already missed)
  • ➖ Risky if Codex changes event emission or omits linkage details
2. Maintain a persistent index of seen rollouts (local state file)
  • ➕ Reduces repeated header reads even further across restarts
  • ➕ Allows richer debouncing/telemetry about discovery decisions
  • ➖ Adds durability/consistency concerns and migration surface
  • ➖ Still needs robust header parsing and midnight-rollover handling

Recommendation: Keep the PR’s approach: a shared, header-based discovery module reused by watch/import/teardown is the most robust against restarts and Codex quirks (notably the session_id trap). The ruled-out cache and mid-write retry strike a good balance between correctness and scan cost without introducing persistent indexing complexity.

Files changed (8) +691 / -12

Enhancement (4) +409 / -11
CodexSubagentDiscovery.csAdd Codex subagent rollout discovery and hook payload builders +224/-0

Add Codex subagent rollout discovery and hook payload builders

• Introduces shared parsing/enumeration for Codex collab subagent rollouts by reading session_meta and using parent_thread_id + thread_source linkage. Implements retry-safe meta reads (mid-write headers), ruled-out caching for foreign rollouts, descendant BFS discovery for import, and standard subagent-start/stop payload construction.

src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs

CodexSubagentTeardown.csFinalize Codex subagents during parent session-end synthesis +57/-0

Finalize Codex subagents during parent session-end synthesis

• Adds a best-effort teardown routine that re-enumerates on-disk child rollouts, kills their watchers, drains remaining transcript, and posts subagent-stop. Runs under a capped time budget to avoid blocking shutdown and mirrors existing Gemini teardown behavior.

src/Capacitor.Cli/Commands/CodexSubagentTeardown.cs

SessionImporter.csImport Codex descendant subagent rollouts as flat subsessions +26/-6

Import Codex descendant subagent rollouts as flat subsessions

• Extends the Codex import path to enumerate transitive descendant rollouts after importing the parent transcript, then sends lifecycle + batches with vendor="codex" for each child. Threads vendor through SendAgentLifecycle/SendTranscriptBatches so child batches are normalized correctly server-side.

src/Capacitor.Cli/Commands/SessionImporter.cs

WatchCommand.csLive-scan Codex sessions tree and spawn child watchers for subagents +102/-5

Live-scan Codex sessions tree and spawn child watchers for subagents

• Adds a Codex-specific subagent scan in the parent watcher: enumerate child rollouts from the shared sessions tree, post subagent-start (fail-closed), and start a detached child watcher per rollout. Adds ruled-out caching, clarifies idle-end behavior for Codex child watchers, and invokes CodexSubagentTeardown before posting SessionEnded on parent-exit synthesis.

src/Capacitor.Cli/Commands/WatchCommand.cs

Bug fix (2) +22 / -1
CodexSessionRolloutLocator.csGuard rollout locator against matching Codex subagent rollouts +14/-1

Guard rollout locator against matching Codex subagent rollouts

• Updates cwd-based rollout matching to immediately return No when session_meta indicates a subagent (thread_source=subagent or parent_thread_id present). Prevents the daemon from correlating a hosted session to one of its own children that shares cwd.

src/Capacitor.Cli.Daemon/Services/CodexSessionRolloutLocator.cs

CodexImportSource.csExclude Codex subagent rollouts from top-level import discovery +8/-0

Exclude Codex subagent rollouts from top-level import discovery

• Filters discovered transcripts to remove collab subagent rollouts so they are not imported as standalone sessions. Documents and enforces the constraint that a child rollout must never be keyed by session_meta.session_id (it contains the parent’s id).

src/Capacitor.Cli/Commands/CodexImportSource.cs

Tests (2) +260 / -0
CodexSessionRolloutLocatorTests.csAdd tests ensuring locator rejects subagent rollouts +30/-0

Add tests ensuring locator rejects subagent rollouts

• Adds two unit tests verifying that MatchRollout returns No when session_meta indicates a subagent, even with matching cwd. Covers both thread_source=subagent and parent_thread_id-only variants.

test/Capacitor.Cli.Tests.Unit/CodexSessionRolloutLocatorTests.cs

CodexSubagentDiscoveryTests.csAdd unit tests for CodexSubagentDiscovery parsing and enumeration +230/-0

Add unit tests for CodexSubagentDiscovery parsing and enumeration

• Introduces coverage for the child id vs session_id trap, top-level vs subagent detection, mid-write header retry behavior, ruled-out caching, midnight day-dir rollover discovery, descendant flattening semantics, and agent type derivation fallbacks.

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1cc14c47e3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// import each child as an unrelated top-level session — and the child's session_meta
// `session_id` field even holds the PARENT's id (its own id is in `id`), so nothing
// downstream may ever key a child by `session_id`.
transcripts = [.. transcripts.Where(t => CodexSubagentDiscovery.TryReadMeta(t.FilePath) is not { IsSubagent: true })];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Retry uncertain headers before treating rollouts as top-level

When an import overlaps creation of a collab child rollout, TryReadMeta deliberately returns null for a partial, locked, or temporarily unreadable header, but this predicate retains that rollout as a top-level session. It can then be imported under the child ID as an unrelated session and later imported again beneath its parent once the header becomes readable, leaving persistent duplicate/mis-associated history. Treat the unknown result as retryable or skip it for this discovery pass rather than classifying it as non-subagent.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b46a545ReadHeader now returns a tri-state verdict, and import discovery keeps only DEFINITIVE non-subagents: an Indeterminate header (empty/truncated — a session actively starting mid-import) is skipped for that discovery pass and picked up by the next run, so a child can no longer be imported top-level during the creation window.

Comment on lines +157 to +159
await SendAgentLifecycle(
httpClient, baseUrl, sessionId, sub.ChildDashlessId, subType, sub.FilePath, cwd,
transcriptPath, progress, vendor: "codex");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fail closed before importing Codex subagent content

When /hooks/subagent-start is rejected or remains unavailable after retries, this call still streams the child because SendAgentLifecycle catches the start failure; its transcript sender also defaults to failOnError: false, and the child ID is then reported before the outer import posts session-end. The server therefore may never open the AgentSubsession stream or accept its lines even though the import completes successfully, silently losing the newly supported Codex child history. As in the Gemini/OpenCode descendant import paths, require an acknowledged start and strict transcript delivery before continuing to stop/completion.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b46a545 — the codex descendant import is now fail-closed like the Gemini/OpenCode paths: no content without an acknowledged subagent-start, strict transcript delivery (failOnError: true), and no subagent-stop after a failed tail; a re-import retries idempotently.

@qodo-code-review

qodo-code-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. README missing Codex subagents ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
This PR changes kcap import --codex/live Codex capture to treat collab subagent rollouts as nested
subagents (not top-level sessions). The main README.md does not document this Codex subagent
behavior in the import/recording sections, risking outdated user guidance.
Code

src/Capacitor.Cli/Commands/CodexImportSource.cs[R31-33]

+        // `session_id` field even holds the PARENT's id (its own id is in `id`), so nothing
+        // downstream may ever key a child by `session_id`.
+        transcripts = [.. transcripts.Where(t => CodexSubagentDiscovery.TryReadMeta(t.FilePath) is not { IsSubagent: true })];
Evidence
Rule 7 requires updating README.md in the same PR for user-facing CLI behavior changes. The diff
shows Codex import now explicitly excludes collab subagent rollouts from top-level discovery and
import now walks Codex descendant rollouts as subagents, but the README’s historical import section
lacks any Codex subagent-specific documentation.

CLAUDE.md: Update README.md in the same PR for any user-facing CLI surface change
src/Capacitor.Cli/Commands/CodexImportSource.cs[31-33]
src/Capacitor.Cli/Commands/SessionImporter.cs[148-162]
README.md[593-629]

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 PR changes user-facing CLI behavior for Codex sessions (collab subagent rollouts are excluded from top-level discovery and imported as subagents), but `README.md` doesn’t mention this Codex-specific subagent behavior.

## Issue Context
Codex CLI 0.146+ (`multi_agent_version: v2`) writes collab subagents into separate rollouts under `~/.codex/sessions/...` and this PR changes both import and live capture semantics to handle them.

## Fix Focus Areas
- README.md[593-629]
- src/Capacitor.Cli/Commands/CodexImportSource.cs[31-33]
- src/Capacitor.Cli/Commands/SessionImporter.cs[148-162]

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



Remediation recommended

2. Teardown auth may mismatch baseUrl ✗ Dismissed 🐞 Bug ☼ Reliability
Description
CodexSubagentTeardown.PostStopAsync creates an authenticated HttpClient without passing the baseUrl
it later posts to; when the process is configured for a different server URL, client auth resolution
can target the wrong server and subagent-stop may fail.
Code

src/Capacitor.Cli/Commands/CodexSubagentTeardown.cs[R48-51]

+        using var client  = await HttpClientExtensions.CreateAuthenticatedClientAsync();
+        var       payload = CodexSubagentDiscovery.BuildStopPayload(sessionId, agentId, agentType, subFile);
+        using var content = new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json");
+        await client.PostWithRetryAsync($"{baseUrl}/hooks/subagent-stop", content);
Evidence
PostStopAsync posts to the baseUrl parameter but constructs the authenticated client without that
same URL. HttpClientExtensions defaults baseUrl from AppConfig/KCAP_URL when omitted, so an override
can cause a mismatch between token/server selection and the actual request URL.

src/Capacitor.Cli/Commands/CodexSubagentTeardown.cs[47-52]
src/Capacitor.Cli.Core/HttpClientExtensions.cs[73-78]

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

### Issue description
`CodexSubagentTeardown.PostStopAsync` posts to the provided `baseUrl`, but calls `HttpClientExtensions.CreateAuthenticatedClientAsync()` without passing that `baseUrl`. `CreateAuthenticatedClientAsync` resolves its target server URL from config/env when `baseUrl` is null; if that differs from the passed-in `baseUrl`, the client can select/validate tokens against a different server than the one receiving the request.

### Issue Context
This is configuration-dependent (shows up when a caller provides a non-default `baseUrl`), but the fix is straightforward and makes the method internally consistent.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CodexSubagentTeardown.cs[47-52]
- src/Capacitor.Cli.Core/HttpClientExtensions.cs[73-78]

### Suggested change
Change to `CreateAuthenticatedClientAsync(baseUrl, ...)` (and ideally thread through a cancellation token from the teardown time-budget path) so auth resolution and request target are aligned.

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


3. Malformed rollouts re-scanned forever ✗ Dismissed 🐞 Bug ➹ Performance
Description
CodexSubagentDiscovery.TryReadMeta returns null when the first parsed JSON line is not
type="session_meta", so EnumerateSubagentRollouts treats permanently malformed rollouts as
“mid-write” and never adds them to ruledOut, re-opening them on every polling tick.
Code

src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs[R55-58]

+                using var doc  = JsonDocument.Parse(line);
+                var       root = doc.RootElement;
+
+                if (root.Str("type") != "session_meta" || root.Obj("payload") is not { } payload) return null;
Evidence
TryReadMeta returns null even after successful JSON parsing when the record isn’t session_meta;
EnumerateSubagentRollouts treats null as a retryable mid-write and skips ruledOut caching. The
codebase already expects non-session_meta-first-line rollouts to exist as malformed transcripts, so
this can cause repeated work in live polling.

src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs[41-46]
src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs[55-59]
src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs[102-107]
test/Capacitor.Cli.Tests.Unit/Codex/CodexImportTests.cs[105-121]

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

### Issue description
`CodexSubagentDiscovery.TryReadMeta` returns `null` not only for mid-write/unreadable rollouts, but also when JSON parsing succeeds and the first non-blank record is not a `session_meta` envelope. `EnumerateSubagentRollouts` interprets any `null` as “header mid-write — retry next tick” and therefore never adds such files to `ruledOut`, causing repeated open/parse attempts on every scan tick.

### Issue Context
The repo already anticipates “first line is not session_meta” as a malformed transcript condition in Codex import tests. Those permanently malformed files should be treated as definitive non-children for polling-caching purposes (or otherwise prevented from being retried forever).

### Fix Focus Areas
- src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs[55-59]
- src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs[102-112]
- test/Capacitor.Cli.Tests.Unit/Codex/CodexImportTests.cs[105-121]

### Suggested change
Introduce a distinction between:
1) retryable failures (I/O errors, JSON parse errors, truncated line) and
2) definitive-but-not-session_meta (valid JSON but wrong `type` / missing payload).

For (2), return a non-null `RolloutMeta` with `IsSubagent = false` (and null ids) so callers can cache it in `ruledOut`, or change `TryReadMeta` to return an explicit status enum/result object and have `EnumerateSubagentRollouts` add definitive-invalid headers to `ruledOut`.

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


4. Null meta imports subagents ✓ Resolved 🐞 Bug ≡ Correctness
Description
CodexImportSource excludes subagent rollouts only when TryReadMeta successfully parses
IsSubagent=true; if TryReadMeta returns null (unreadable/mid-write/invalid header), the rollout
remains in top-level discovery and can be imported as an unrelated session.
Code

src/Capacitor.Cli/Commands/CodexImportSource.cs[R31-33]

+        // `session_id` field even holds the PARENT's id (its own id is in `id`), so nothing
+        // downstream may ever key a child by `session_id`.
+        transcripts = [.. transcripts.Where(t => CodexSubagentDiscovery.TryReadMeta(t.FilePath) is not { IsSubagent: true })];
Evidence
The import discovery filter only removes files where TryReadMeta returns a non-null meta with
IsSubagent=true; TryReadMeta is documented to return null for unreadable or not-yet-parseable
headers, so those files will incorrectly remain discoverable as top-level sessions.

src/Capacitor.Cli/Commands/CodexImportSource.cs[24-34]
src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs[41-46]

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

### Issue description
`CodexImportSource.DiscoverAsync` filters out Codex collab subagent rollouts by calling `CodexSubagentDiscovery.TryReadMeta(...)`, but it only excludes files when parsing succeeds and `IsSubagent == true`. When `TryReadMeta` returns `null` (unreadable/mid-write/malformed header), the file is treated as “not a subagent” and stays in the top-level discovery set, enabling incorrect top-level imports.

### Issue Context
`TryReadMeta` explicitly returns `null` for unreadable or not-yet-parseable `session_meta` headers, so callers should treat `null` as “unknown” and retry/skip, not as “definitely not a subagent”.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CodexImportSource.cs[24-34]
- src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs[41-46]

### Suggested change
Adjust the filter to only keep files when `TryReadMeta(...)` returns a non-null meta with `IsSubagent == false` (and optionally retry/skip `null` results).

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


Grey Divider

Context used
⚠️ Tickets: not configured — ticket URL found in PR but could not be fetched — check ticket provider credentials

Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/Capacitor.Cli/Commands/CodexImportSource.cs Outdated
Comment thread src/Capacitor.Cli/Commands/CodexImportSource.cs Outdated
Comment thread src/Capacitor.Cli.Core/CodexSubagentDiscovery.cs Outdated
Comment thread src/Capacitor.Cli/Commands/CodexSubagentTeardown.cs Outdated
…down auth, README

- CodexSubagentDiscovery.ReadHeader replaces TryReadMeta with a tri-state
  verdict: Subagent / NotSubagent are DEFINITIVE (cacheable — a permanently
  malformed first line is ruled out instead of re-opened every polling tick),
  Indeterminate (empty / EOF-truncated line / IO error) is retried and never
  cached. Completeness is judged by newline presence, with a parseable-but-
  unterminated session_meta still judged on content.
- Import discovery keeps only DEFINITIVE non-subagents: an indeterminate
  header (a session actively starting mid-import) is skipped for that pass
  rather than risking a child imported top-level now and nested next run.
- The codex descendant import is now fail-closed like Gemini/OpenCode: no
  content without an acknowledged subagent-start, strict transcript delivery
  (failOnError), and no subagent-stop after a failed tail — a re-import
  retries idempotently.
- CodexSubagentTeardown threads baseUrl into auth resolution so token/server
  selection matches the URL posted to.
- README: Codex collab subagent capture + import nesting documented; the
  issue-tracker token in a test comment removed (CI rule).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexeyzimarev

Copy link
Copy Markdown
Member Author

CI triage:

  • No Linear issue IDs in C# source — was a genuine hit (an issue-tracker token in a test comment); removed in b46a545.
  • Build and test (windows-latest) — fails on NotAuthenticated_MakesNoRequest_AndReturnsZero, a test that does not exist on this branch's base; it comes from newer main via the merge ref, and the same test fails on main's own latest CI run (run 31416066534, commit 032f477) — a main regression from the recent update-check work, unrelated to this PR. This PR should go green on that job once main is fixed.

All six bot findings (2 Codex P1 + 4 Qodo) addressed in b46a545 — see the inline replies.

@alexeyzimarev

Copy link
Copy Markdown
Member Author

Windows CI addendum: the re-run now fails on a different main-side test — SetupFunnelTests.WorkOSDiscovery_emits_signin_completed_before_tenant_none_for_a_zero_tenant_run (from the #501 telemetry funnel work; passes locally and on ubuntu). Main's own last two runs also failed the Windows job (on this and on NotAuthenticated_MakesNoRequest_AndReturnsZero), so the Windows lane is currently flaky/red on main independently of this PR. Everything this PR can control is green: Linear-ID check, ubuntu build+test, both AOT publishes.

@alexeyzimarev

Copy link
Copy Markdown
Member Author

Windows lane root-caused far enough to attribute definitively: the run that introduced SetupFunnelTests (main 5d5472f46, #501's own merge commit) failed its Windows job on the exact same testWorkOSDiscovery_emits_signin_completed_before_tenant_none_for_a_zero_tenant_run. Since then main's Windows lane alternates between that and NotAuthenticated_MakesNoRequest_AndReturnsZero, one per run, while ubuntu stays green. The failure shape (guard passes → executed path provably emits → sink still empty, under --maximum-parallel-tests 1) points at serial process-global state poisoning in the telemetry/update-check test families.

Filed as AI-1848 with the full run table and starting points. Re-ran the failed job on this PR — main's 629d4426e run passed the same lane, so a re-run can go green.

@alexeyzimarev
alexeyzimarev merged commit 8d933b0 into main Aug 10, 2026
10 of 11 checks passed
@alexeyzimarev
alexeyzimarev deleted the alexeyzimarev/ai-1839-codex-collab-subagent-capture branch August 10, 2026 19:10
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