codex exec --ephemeral --json: expose provider-reported model ID on turn.completed

Open 💬 1 comment Opened Aug 19, 2026 by tgh612

What version of Codex CLI is running?

codex-cli 0.147.0

What platform is your computer?

macOS 26.5.2 (arm64)

Authentication mode

ChatGPT authentication

Feature request

Please expose the provider-reported response model in the JSONL emitted by codex exec --json, including when the invocation is --ephemeral.

The proposed additive field is:

{
  "type": "turn.completed",
  "usage": {
    "input_tokens": 123,
    "cached_input_tokens": 45,
    "output_tokens": 67
  },
  "server_model": "provider-reported-model-id"
}

server_model should be copied from provider response metadata for that completed turn. It must not be inferred or backfilled from --model, local configuration, alias resolution, the startup banner, rollout context, a generic model field, or model-authored output.

Why this is needed

I am building a governed evaluation harness around codex exec --ephemeral --json. The requested model and configured/resolved model are useful diagnostics, but they do not independently establish which model identifier the provider returned for a turn. That prevents a pinned-model evaluation from detecting silent rerouting or failing closed when observed identity is unavailable.

The current ephemeral JSONL includes lifecycle, item, completion, and detailed usage events, but the runs I inspected did not include provider-response model identity. Dropping --ephemeral to inspect persisted rollout context would weaken the sealed-task non-persistence boundary, and rollout/banner values still describe configured or resolved state rather than the provider response.

This is the codex exec JSONL counterpart to #33880, which requests provider-reported response model data for app-server v2.

Minimal reproduction shape

No production prompt is needed:

codex exec \
  --ephemeral \
  --json \
  --model PINNED_MODEL_ID \
  --sandbox read-only \
  "Reply with exactly OK."

The terminal turn.completed event reports usage but not the provider-returned model identifier.

Requested semantics

  • Type: a non-empty string when present.
  • Provenance: provider response metadata for the completed turn.
  • Representation: the provider-returned identifier, not a client-side alias substitution.
  • Missing metadata: omit or emit null; never fall back to the requested/configured model.
  • Streaming: retain the value through aggregation and emit it on the terminal turn event.
  • Conflict: if one logical turn observes multiple distinct non-empty provider model IDs, emit an explicit conflict/error rather than choosing one silently.
  • Ephemeral parity: emit the field with --ephemeral without persisting a rollout.
  • Privacy: do not add prompts, responses, auth material, headers, account identifiers, or a telemetry requirement.

An adjacent server_provider field could be useful, but it is not a substitute for server_model.

Acceptance cases

| Case | Requested | Provider metadata | Required result |
|---|---|---|---|
| Match | model-a | model-a | server_model: "model-a" |
| Reroute | model-a | model-b | server_model: "model-b" |
| Missing | model-a | absent | omitted/null, never model-a |
| Conflict | model-a | model-a, then model-b | explicit conflict/error |
| Alias | alias-a | dated-model-a | server_model: "dated-model-a" |
| Ephemeral | any | present | field emitted; no rollout persisted |

Existing turn.completed.usage values should remain unchanged, and consumers that ignore unknown fields should continue to work.

Assurance limit

A provider-reported identifier is still a provider assertion, not cryptographic weight attestation. The goal is narrower: make the served identifier observable independently of the client-side request so evaluation tooling can detect a requested-versus-served mismatch and fail closed when the value is absent.

View original on GitHub ↗

1 Comment

tgh612 · 8 days ago

Follow-up reproduction: a single public codex exec --ephemeral --json invocation on Codex CLI 0.147.0 completed and emitted turn.completed, but that event still contained no server_model. The consumer therefore left observed identity unavailable and failed closed rather than substituting the requested/configured model. There was no retry, and no raw log, session identifier, or attachment is included.