Skip to content

fix(server): Codex subagent approvals show up in the parent thread - #13703

Merged
juliusmarminge merged 1 commit into
t3code/codex-turn-mappingfrom
v2/codex-subagent-approvals-in-parent
Sep 25, 2026
Merged

juliusmarminge merged 1 commit into
t3code/codex-turn-mappingfrom
v2/codex-subagent-approvals-in-parent

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

A Codex multi-agent V2 subagent inherits the root's approval policy, and Codex asks for its approvals on the child's own native thread and turn. The adapter wrote the approval node, runtime request and card to the child thread. The sidebar hides that thread, and the parent's "Pending Approval" pill only reads the parent's own requests. So the user never saw the request, and the parent looked like it was still working while Codex waited.

Follows #13624, which has merged.

What changed

  • Adapter. buildApprovalRequestArtifacts in CodexAdapterV2 walks from the requesting turn up to the top-level turn. It writes the approval node, the runtime request and the approval_request card to that turn's thread, run and provider turn. For nested subagents this is the top-level ancestor. This follows the OpenCode pattern in the opencode_child_approval fixture.
    • The node's parent is the subagent node that asked, so the request is tied to that subagent in the projection.
    • The pending map is still keyed by request id, so answering from the parent completes Codex's server request.
    • All Codex approval requests go through this one builder: command, file change, permissions, MCP elicitation, and the legacy execCommandApproval / applyPatchApproval.
    • No contract change. The child keeps its command and answer in its own timeline, and it holds no request of its own.
  • Fixture. subagent_v2_approval was recorded live on Codex 0.156.1 with gpt-6-luna, using the adapter's approval-required defaults (untrusted, readOnly). The parent spawns one subagent, which must write a file. The recording shows item/commandExecution/requestApproval arriving with the child's threadId and turnId. The recorder gains this scenario.
  • Scenario step. approve_next_runtime_request can take a shell snapshot while the request is pending. The fixture uses it to check the parent shell's pendingRuntimeRequest.

Not attributed on the card

Neither the approval_request turn item nor ThreadPendingApproval has a field that names the requesting subagent. OpenCode's parent-side card does not say which subagent asked either. Labeling the card would need a new optional contract field plus web and mobile rendering, so it is left for a follow-up. For now the link exists only in the projection, as node.parentNodeId.

Kept: the child-thread question path from #13624

I did not remove the composer under ProviderSubagentBar or the runtimeLayer.test.ts child-question test. OpenCode can still leave a pending request on a native child thread.

  • requestOwnerState returns the child session's own state when the child has an active turn (OpenCodeAdapterV2.ts ~2039).
  • createChildTurn gives that turn the child's app thread (~2313).
  • So a permission.asked or question.asked from a child that is already running lands on the child thread. Only asks that arrive before the child relation is known go through routeChildRequest to the root. Those are what opencode_child_approval covers.
  • Under approval-required, child bash rules are ask, per OpenCodeAdapterV2.test.ts.

Codex itself can no longer do this.

  • Subagent questions are refused in Codex 0.156.1: request_user_input returns an error for non-root agents (core/src/tools/handlers/request_user_input.rs:69), and the async tools are only registered for the root (core/src/tools/spec_plan.rs:1124, :1147).
  • Codex MCP tool approvals through request_user_input deny non-root agents (core/src/mcp_tool_call.rs:1675).
  • The skill MCP dependency prompt only runs for first-party originators (core/src/mcp_skill_dependencies.rs:48). T3 is t3code_desktop.

Claude, ACP (Grok and registry), Cursor and Pi build every request from the root turn's context. Moving OpenCode's child asks to the root would be a separate change.

Verification

  • vp test run src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t subagent_v2_approval fails on the unfixed adapter: no pending request ever appears on the parent thread, and the respond step times out after 60 s. With the fix it passes.
  • On the rebased head, these ran together: OrchestratorReplayFixtures.integration.test.ts (full suite), CodexReplayFixtures.integration.test.ts, OrchestratorReplayFixtures.contract.test.ts, Adapters/CodexAdapterV2.test.ts and runtimeLayer.test.ts. 255 passed.
  • vp exec tsc --noEmit -p . in apps/server: no errors or warnings. vp run knip:check passes.
  • vp lint on the touched files shows only warnings already on the base branch: two unused declarations in CodexAdapterV2.ts and assertUserMessagesExclude.
  • Recorded with CODEX_HOME=<isolated copy> T3_CODEX_BIN=<codex 0.156.1> node scripts/record-codex-app-server-replay-fixture.ts --scenario subagent_v2_approval in a disposable git repo. The recorder's scrubbing replaced the home path, hostname and installationId. The copied CODEX_HOME was deleted afterwards.
  • Not run: web, mobile and client-runtime tests (no client code changed), and a real-client pass.

Model: Claude Opus 5.5 (Claude Code)

🤖 Generated with Claude Code


Devin Review

A multi-agent V2 subagent inherits the root's approval policy, and Codex
asks for its approvals on the child's own native thread and turn. The
adapter wrote the approval node, runtime request and card to the child
thread, which the sidebar hides, so the user never saw the request and
the parent looked like it was still working.

The approval is now asked on the top-level thread and run, hung off the
subagent that asked. The pending map is still keyed by request id, so
answering from the parent completes Codex's server request. This covers
command, file change, permission and MCP elicitation approvals and the
legacy exec/apply-patch approvals, which all build through
buildApprovalRequestArtifacts.

The subagent_v2_approval fixture is recorded live on Codex 0.156.1 with
gpt-6-luna in approval-required mode.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 25, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 55bf66b

Macroscope's review found this PR approvable — This is a localized Codex adapter bug fix that makes subagent approvals visible in the parent thread while preserving the child’s native execution and request handling. The added replay fixture and assertions cover the routing and pending-state behavior without changing product defaults or static-analysis configuration.

You can add or adjust custom eligibility rules. Learn more.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 4.9 KiB — 6.8 KiB ✅
Codex Thread snapshot wire — 3.7 KiB — 4.9 KiB ✅
Codex Live turn WebSocket wire — 1.1 KiB — 2.0 KiB ✅
Codex Live turn WebSocket decoded — 20.4 KiB — 29.3 KiB ✅
Codex Live turn messages — 1 — 8 ✅
Claude Total thread wire — 4.9 KiB — 6.8 KiB ✅
Claude Thread snapshot wire — 3.7 KiB — 4.9 KiB ✅
Claude Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Claude Live turn WebSocket decoded — 20.8 KiB — 29.3 KiB ✅
Claude Live turn messages — 2 — 8 ✅

Baseline: unavailable · PR result: 55bf66b · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 106.1 KiB
  • Claude decoded thread snapshot: 106.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@juliusmarminge
juliusmarminge merged commit e1b6476 into t3code/codex-turn-mapping Sep 25, 2026
24 of 25 checks passed
@juliusmarminge
juliusmarminge deleted the v2/codex-subagent-approvals-in-parent branch September 25, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant