App-server: propagate originating client input ID to tool execution context

Open 💬 2 comments Opened Aug 4, 2026 by butterflysky-syne

What variant of Codex are you using?

Codex CLI/app-server v2 (codex-cli 0.145.0) through an external app-server client using turn/start, turn/steer, lifecycle hooks, MCP tools, and client-executed dynamic tools.

What feature would you like to see?

Carry the existing clientUserMessageId from the user input that initiated a model continuation into the tool calls produced by that continuation, as immutable model-inaccessible execution metadata.

Today clientUserMessageId lets an app-server client correlate a turn/start or turn/steer input with its UserMessageItem, but the correlation stops at the input item. At tool execution:

  • PreToolUse exposes session_id, turn_id, tool_use_id, tool_name, and tool_input;
  • experimental item/tool/call exposes threadId, turnId, callId, tool, and arguments;
  • MCP dispatch has the model-authored arguments, but no immutable identifier for the input/continuation that produced the call.

turn_id is not sufficient because multiple turn/steer inputs can be accepted within one active turn. A policy engine can therefore know that an input was authentic and that a tool call belongs to the turn, but cannot prove which input the call descends from. Reading a mutable "latest input" cursor at PreToolUse time is racy: input B can arrive after a tool call was created under input A but before that call is dispatched.

Requested protocol shape (names are illustrative):

  1. Snapshot originatingClientUserMessageId when each model continuation begins or, at latest, when each output/tool-call item is created.
  2. Preserve that value immutably on the tool-call item for its lifetime.
  3. Include it in PreToolUse and PostToolUse hook input.
  4. Include it in DynamicToolCallParams for item/tool/call.
  5. Make it available to MCP dispatch as reserved request metadata (not inside model-authored tool arguments).

The field should remain opaque to Codex and excluded from model-visible prompt/tool schemas. The app-server client remains responsible for interpreting and authorizing it. Calls without an attributable input should carry null/an explicit unavailable state rather than silently inheriting the latest input.

This enables external controllers to implement provenance, audit, confused-deputy prevention, and per-input policy without parsing prompt text or trusting a root identifier selected by the model.

Additional information

The cheapest falsifying integration test is a steer race:

  1. Start input A with clientUserMessageId = A and cause a harmless tool call to be created.
  2. Before that call reaches PreToolUse/dispatch, steer input B with clientUserMessageId = B.
  3. Assert the existing call carries A at item creation, hook execution, and dispatch.
  4. Cause a later tool call from the continuation consuming B and assert it carries B.
  5. Assert neither tool arguments nor model output can supply, replace, or rewrite the originating ID.

Additional coverage should include identical prompt text under different client IDs, subagent calls, compaction/resume, interruption, and inputs with no client ID.

Adjacent issue #32254 requests idempotency and durable reconciliation for clientUserMessageId. This request is orthogonal: it carries that already-correlated input identity forward to descendant effects.

The immediate motivating system is a durable external message ingress that must distinguish "this message existed somewhere in the turn" from "this effect was produced under this message." The same seam is useful for any external workflow controller enforcing per-input policy.

View original on GitHub ↗

2 Comments

ded-furby · 23 days ago

Thanks for the clear proposal. One concrete addition beyond your test plan I’d suggest is carrying this through the existing continuation item metadata early ( + local continuation state) so model-visible payloads never touch it, and then surfacing the same immutable value to both and MCP dispatch context. That should prevent any dependence on cursor ordering and also keeps policy engines deterministic across resume/subagent races. If helpful, I can prepare a protocol-ready patch + integration scenario once this request is accepted/invited.

ded-furby · 23 days ago

Thanks for the clear proposal. One concrete addition beyond your test plan I would add is to propagate the field through continuation metadata at item-creation time so model-visible payloads never include it, then pass the same immutable value to both turn hook context and MCP dispatch context. That avoids dependence on any mutable latest-input cursor and keeps policy decisions deterministic across resume/subagent races. If helpful, I can prepare a protocol-ready patch + integration test scenario once this request is accepted/invited.