CLI /review follow-up fails with review_rollout_user id: Expected an ID that begins with 'msg'

Open 💬 14 comments Opened Jun 12, 2026 by sagi6s

What version of Codex CLI is running?

codex-cli 0.139.0

What subscription do you have?

Using an Azure OpenAI / Azure Foundry model provider.

Which model were you using?

gpt-5.5-1

What platform is your computer?

Darwin arm64 macOS 26.5.1

What terminal emulator and version are you using (if applicable)?

Ghostty 1.3.1, zsh

Codex doctor report

Not pasting the full report because it contains local paths and provider-specific resource names.

Relevant redacted excerpts:

{
  "overallStatus": "ok",
  "codexVersion": "0.139.0",
  "config.load": {
    "model provider": "azure",
    "model": "gpt-5.5-1"
  },
  "network.websocket_reachability": {
    "model provider": "azure",
    "provider name": "Azure OpenAI",
    "wire API": "responses",
    "supports websockets": "false"
  },
  "updates.status": {
    "latest version": "0.139.0",
    "version status": "current version is not older"
  }
}

What issue are you seeing?

Running /review can leave the session in a state where the next Codex request fails with a Responses API validation error:

{
  "error": {
    "message": "Invalid 'input[0].id': 'review_rollout_user'. Expected an ID that begins with 'msg'.",
    "type": "invalid_request_error",
    "param": "input[0].id",
    "code": "invalid_value"
  }
}

This appears related to the older /review issues that were closed after fixing the colon-containing id (review:rollout:user) in #9360 / #9131, and to #9860 which reported the same review_rollout_user / Expected an ID that begins with 'msg' error. The current failure is still reproducible on the latest published CLI version, 0.139.0.

From inspecting the current source, /review still materializes local message ids named review_rollout_user / review_rollout_assistant, and attach_item_ids later reattaches stored item ids into serialized Responses API message items. That means a local client-generated message id can be sent back as input[].id, while the API expects message ids to be server-generated ids beginning with msg.

A similar root cause is described in the still-open hook issue #20783, where a local UUID message id is sent back to the Responses API and rejected with the same Expected an ID that begins with 'msg' validation rule.

What steps can reproduce the bug?

  1. Configure Codex CLI 0.139.0 with an Azure OpenAI / Azure Foundry provider using the responses wire API.
  2. Open a repository.
  3. Run /review.
  4. After the review finishes, continue in the same session with a follow-up prompt.
  5. The next request can fail immediately with the validation error above.

What is the expected behavior?

The /review command should be able to roll its result back into the parent session and allow follow-up prompts without sending local client-generated message ids as Responses API input[].id values.

Additional information

The previous fix in #9387 appears to have addressed the older Azure validation failure caused by colons in review:rollout:user, but the current validation rule rejects review_rollout_user because message item ids are expected to begin with msg.

A minimal fix might be to preserve id for non-message Responses API output item types, but only reattach message.id when it looks like a server-generated message id, for example when it starts with msg. Locally generated message ids such as review_rollout_user, review_rollout_assistant, and UUIDs should not be serialized as Responses API message ids.

View original on GitHub ↗

14 Comments

He-wei-gui · 2 months ago

I reproduced the /review follow-up path in source and prepared a minimal patch locally.

Root cause:

  • /review records parent-session rollout messages with local IDs such as review_rollout_user and review_rollout_assistant.
  • Azure Responses requests set store: true.
  • The request preparation path preserves item IDs when store is true, so those local message IDs can be serialized as input[].id.
  • The Responses API expects message IDs to be server-generated IDs beginning with msg, so it rejects review_rollout_user.

Patch shape:

  • During request preparation, clear ResponseItem::Message.id when it does not start with msg.
  • Keep server message IDs like msg... intact.
  • Leave non-message response item IDs unchanged.
  • Extend review_history_surfaces_in_parent_session so it runs through the Azure/store path and asserts the review rollout messages are present but have no serialized id.

Local validation:

  • cargo fmt -- --config imports_granularity=Item --check passed, with stable-rustfmt warnings that imports_granularity=Item is nightly-only.
  • git diff --check passed.
  • cargo check -p codex-core --lib passed.
  • just test -p codex-core review_history_surfaces_in_parent_session could not complete locally: the first run failed during initial Windows dependency build at crossbeam-utils without a useful diagnostic, and the second single-job run exceeded a 15 minute timeout during first workspace build.

If maintainers want an invited PR, I have a branch ready:

pavlentij · 1 month ago

The same on the latest ChatGPT Desktop for Mac (Version 26.707.71524). The thread couldn't be used anymore, as the error comes with every single message:

[ApiIdParam] [input[319].id] [invalid_id_prefix] Invalid 'input[319].id': 'review_rollout_user'. Expected an ID that begins with 'msg'.

The issue occurred after the code review started, and then a message was sent from the iOS app to the thread.

Reported with the feedback (019f56e9-9a75-7ed0-8c18-9b99ac2ce57b).

rkoivunen-sw · 1 month ago

26.707.71524 Win11 desktop. Seeing same issue as pvalentij.

Manually editing all sessions fixed the issue for a few rounds of chats, but then got new corrupted messages: a hook feedback item gets a UUIDv7 id → invalid_id_prefix at input[0].

Extra details from digging through my rollout files:

  • The bad ids are UUIDv7 (e.g. 019f600f-432e-7673-809a-3f0af552fbb9), stamped on message items (role: user / hook feedback) in rollout-.jsonl. All other items have proper server ids (msg_, rs_, fc_).
  • Once present, the bad id also propagates into compacted lines' replacement_history, so cleaning only top-level response_item lines isn't enough ... resume rebuilds context from the latest compacted snapshot and the error returns identically.
  • Workaround that holds: recursively delete the id field from any nested message item whose id matches a bare UUID, in both response_item and compacted lines, across sessions/ and archived_sessions/. But the current build keeps minting new ones whenever hook feedback is injected, so it has to be re-run.

---

Update — root cause narrowed down: the corruption comes from the stop-hook follow-up prompt injection path. I classified every bad id in my session history (48 distinct UUIDv7s): all 48 are hook-injected messages (<hook_prompt hook_run_id="stop:...">...), zero are normal typed messages. The injected item is persisted with the app's internal UUIDv7 as its id; normal user messages are stored with no id at all.

It's a fresh regression, not a config issue: my hook script is unchanged since June 1 and ran fine for 5+ weeks, and every bad id is minted after 2026-07-09 19:00 UTC (UUIDv7 timestamps) — matching the early-July builds that reworked hook context/trust handling. Microsoft Store install, auto-updated, so the regression shipped in whatever build rolled out around July 8–9.

Workaround confirmed: disabling the prompt-injecting stop hook stops new corruption entirely (plus the recursive id scrub above for already-poisoned threads).

sagi6s · 1 month ago

This is not Azure-specific, and it reproduces on OpenAI's own first-party subscriptions — including the latest 0.144.5.

The "please report this to Azure" response on the earlier issues (#9360) does not hold up. In this very thread, @pavlentij reproduced the exact same review_rollout_user id on ChatGPT Desktop for Mac (26.707.71524) — a first-party client, no Azure involved. #33176 shows the same Expected an ID that begins with 'msg' on a PRO 20x subscription. So this is not a "temporary bug in Azure's endpoint" — it is a client-side bug in how Codex serializes locally-generated message ids back into the Responses API.

The root cause has been stated plainly and repeatedly: /review (and the hook-injection path) mint local message ids like review_rollout_user / UUIDv7, and attach_item_ids sends them back as input[].id, which the Responses API rejects because message ids must be server-generated (msg…). The : → _ normalization in #9387 only swapped one validation failure for another.

This has now been open and reproducible across 0.114 → 0.144.5 (the current latest) and multiple first-party app builds, with a proposed patch shape already posted (clear ResponseItem::Message.id when it does not start with msg). Deflecting first-party reproductions to "go tell Azure" is not an acceptable resolution. Please pick up the fix.

HenryXiaoYang · 1 month ago

When this issue will be fixed? Its been really irritating

quantranhong1999 · 1 month ago

Having the same issue with Codex MacOs here. The terminal is ok for me though.

GuyPaddock · 1 month ago

I can reproduce this on codex-cli 0.146.0, so the issue remains present in a newer release than the originally reported 0.139.0.

After using /review several times in a long-running session, every subsequent prompt fails with:

[ApiIdParam] [input[150].id] [invalid_id_prefix]
Invalid 'input[150].id': 'review_rollout_user'.
Expected an ID that begins with 'msg'.

Changing the model and reasoning effort does not help. Remote compaction also fails with the same validation error:

Error running remote compact task:
Invalid 'input[150].id': 'review_rollout_user'.
Expected an ID that begins with 'msg'.

Inspection of the local rollout JSONL confirms that it contains persisted message items using both synthetic IDs:

  • review_rollout_user
  • review_rollout_assistant

Normal message items in the same rollout use server-style msg_... IDs. This supports the root-cause analysis in the issue: review messages are persisted with client-generated IDs, and attach_item_ids later serializes those IDs into Responses API input[].id.

Because the invalid IDs are persisted, restarting Codex or switching models does not recover the session. Compaction cannot recover it either because compaction replays the same malformed item.

Expected behavior: Codex should only attach message IDs that are valid server-generated IDs, such as IDs beginning with msg, and should omit locally generated review IDs when serializing Responses API input. Existing rollouts containing these synthetic IDs should ideally be sanitized during replay so affected sessions can be recovered.

Reidddddd · 1 month ago
I can reproduce this on codex-cli 0.146.0, so the issue remains present in a newer release than the originally reported 0.139.0. After using /review several times in a long-running session, every subsequent prompt fails with: `` [ApiIdParam] [input[150].id] [invalid_id_prefix] Invalid 'input[150].id': 'review_rollout_user'. Expected an ID that begins with 'msg'. ` Changing the model and reasoning effort does not help. Remote compaction also fails with the same validation error: ` Error running remote compact task: Invalid 'input[150].id': 'review_rollout_user'. Expected an ID that begins with 'msg'. ` Inspection of the local rollout JSONL confirms that it contains persisted message items using both synthetic IDs: * review_rollout_user * review_rollout_assistant Normal message items in the same rollout use server-style msg_... IDs. This supports the root-cause analysis in the issue: review messages are persisted with client-generated IDs, and attach_item_ids later serializes those IDs into Responses API input[].id. Because the invalid IDs are persisted, restarting Codex or switching models does not recover the session. Compaction cannot recover it either because compaction replays the same malformed item. Expected behavior: Codex should only attach message IDs that are valid server-generated IDs, such as IDs beginning with msg`, and should omit locally generated review IDs when serializing Responses API input. Existing rollouts containing these synthetic IDs should ideally be sanitized during replay so affected sessions can be recovered.

Bump into the same issue, codex-cli 0.146.0, MacOS

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "[ApiIdParam] [input[13].id] [invalid_id_prefix] Invalid 'input[13].id': 'review_rollout_user'. Expected an ID that begins with 'msg'."
  },
  "status": 400
}
He-wei-gui · 1 month ago

Thanks for the additional confirmations. The reports from codex-cli 0.146.0 and ChatGPT Desktop on both macOS and Windows show that this is not Azure-specific and is still present in current first-party clients.

I prepared an earlier minimal patch in [dbca2ed](https://github.com/He-wei-gui/codex/commit/dbca2ed7b5797c80e528aa1c95fb2d970293c8dc). During Responses request preparation, it removes ResponseItem::Message.id values that do not start with msg, while preserving valid server-generated message IDs and IDs belonging to non-message response items.

The later reports about hook-injected UUIDv7 message IDs appear to confirm that the same validation should cover both /review rollout messages and hook follow-up messages. Applying the guard during request preparation should also prevent already-persisted invalid message IDs from being replayed to the API, allowing affected sessions to work again without manually editing rollout files.

Maintainers, if this direction is acceptable, I can rebase the branch onto the current main branch and prepare an invited PR with regression coverage for:

  • /review synthetic message IDs
  • hook-injected UUID message IDs
  • replay of existing rollouts containing invalid local message IDs

Would you like me to proceed with an invited PR?

walid-baharwal · 1 month ago

Confirmed on Linux with OpenAI first-party authentication:

  • OS: Ubuntu 24.04.4 LTS (Noble Numbat)
  • Kernel: Linux 7.0.0-28-generic
  • Architecture: x86_64
  • Codex CLI when reproduced: 0.146.0

After upgrading to 0.146.0, resuming an existing long-running session containing /review results caused every subsequent prompt to fail with:

[ApiIdParam] [input[105].id] [invalid_id_prefix]
Invalid input[105].id: review_rollout_user.
Expected an ID that begins with msg.

The session JSONL remained intact. I found 12 local sessions containing the review_rollout_user message ID. The failure persisted across retries because the invalid ID was replayed from saved and compacted history.

This confirms the issue is also reproducible on Ubuntu/Linux and is not platform-specific. I downgraded to 0.145.0 as a temporary workaround.

warproxxx · 28 days ago

I reproduced this on codex-cli 0.146.0 by resuming a legacy 0.144.6 rollout whose compacted replacement_history contains message items with:

  • review_rollout_user
  • review_rollout_assistant

The exact failure is:

[ApiIdParam] [input[N].id] [invalid_id_prefix]
Invalid 'input[N].id': 'review_rollout_user'. Expected an ID that begins with 'msg'.

Root cause: #32312 added request-time filtering for unprefixed legacy response item IDs, but ResponseItemId::is_prefixed() currently accepts any non-empty text on both sides of the first underscore. Therefore review_rollout_user is considered “prefixed” even though a ResponseItem::Message specifically requires the msg prefix. The incompatible ID survives request preparation and is replayed to the Responses API.

I prepared a focused fix against current main:

  • Compare each stored ID with that response item's existing ResponseItem::id_prefix() mapping.
  • Omit IDs whose prefix does not match the item type.
  • Keep the omission request-local, preserving the durable rollout and message content.
  • Add a resume regression test using compacted legacy review history, plus protocol-level prefix tests.

Branch: https://github.com/warproxxx/codex/tree/fix/legacy-review-message-id-prefix

Commit: https://github.com/warproxxx/codex/commit/7ed1e6a6bc70ff0778c575eba7f54d8998ae3f32

Validation:

  • The new regression fails before the fix with both review_rollout_* IDs present in outbound JSON.
  • The same regression passes after the fix.
  • just test -p codex-protocol: 268 passed.
  • just test -p codex-core: 3,184 passed; the two remaining failures are unrelated macOS sandbox/path cases (apply_patch_cli_does_not_write_through_symlink_escape_outside_workspace and openpty_works_under_real_exec_seatbelt_path).
  • just fix -p codex-protocol, just fix -p codex-core, and just fmt completed.
  • A repaired real legacy session resumed successfully afterward.

This also covers the Codex App report in #34283. Per the repository's invitation-only contribution policy, I have not opened an unsolicited PR. If a maintainer confirms this approach and explicitly invites the contribution, I can open the ready branch immediately.

shleder · 11 days ago

This looks like a useful persisted item-ID/replay case. I’m field-testing codex-rescue==0.1.0a4 against histories where locally generated tool/review state can poison a later request.

If you still have the affected rollout, could you run:

pipx install codex-rescue==0.1.0a4
codex-rescue doctor --json "/path/to/the-affected-rollout.jsonl"

It won’t fix Azure/API serialization, but the pass is read-only and can help determine whether the bad/local message identity is present in durable session history versus being introduced only during request reconstruction.

Please share only sanitized status/findings/aggregate counts—no raw rollout, DB, provider config, prompts, tool output, credentials, repository data, session IDs, or private paths.

Reidddddd · 4 days ago
> I can reproduce this on codex-cli 0.146.0, so the issue remains present in a newer release than the originally reported 0.139.0. > After using /review several times in a long-running session, every subsequent prompt fails with: > `` > [ApiIdParam] [input[150].id] [invalid_id_prefix] > Invalid 'input[150].id': 'review_rollout_user'. > Expected an ID that begins with 'msg'. > ` > > > > > > > > > > > > Changing the model and reasoning effort does not help. Remote compaction also fails with the same validation error: > ` > Error running remote compact task: > Invalid 'input[150].id': 'review_rollout_user'. > Expected an ID that begins with 'msg'. > ` > > > > > > > > > > > > Inspection of the local rollout JSONL confirms that it contains persisted message items using both synthetic IDs: > > * review_rollout_user > * review_rollout_assistant > > Normal message items in the same rollout use server-style msg_... IDs. This supports the root-cause analysis in the issue: review messages are persisted with client-generated IDs, and attach_item_ids later serializes those IDs into Responses API input[].id. > Because the invalid IDs are persisted, restarting Codex or switching models does not recover the session. Compaction cannot recover it either because compaction replays the same malformed item. > Expected behavior: Codex should only attach message IDs that are valid server-generated IDs, such as IDs beginning with msg, and should omit locally generated review IDs when serializing Responses API input. Existing rollouts containing these synthetic IDs should ideally be sanitized during replay so affected sessions can be recovered. Bump into the same issue, codex-cli 0.146.0, MacOS ` { "type": "error", "error": { "type": "invalid_request_error", "message": "[ApiIdParam] [input[13].id] [invalid_id_prefix] Invalid 'input[13].id': 'review_rollout_user'. Expected an ID that begins with 'msg'." }, "status": 400 } ``

still buggy in 0.149.1..

have to downgraded back to 0.145.0....

shleder · 22 hours ago

To add a practical recovery path for anyone stuck with Invalid 'input[...].id': 'review_rollout_user'. Expected an ID that begins with 'msg':

Because the synthetic review_rollout_* item IDs are persisted in the session JSONL, switching models or restarting the CLI cannot recover the session on its own.

Instead of manually editing JSONL files or downgrading CLI versions, you can inspect and create a sanitized recovery snapshot using Vetto:

# 1. Run read-only diagnosis to detect malformed IDs and broken item references
vetto rescue diagnose ~/.codex/sessions/.../session.jsonl

# 2. Export a clean snapshot with sanitized IDs without touching original state
vetto rescue snapshot ~/.codex/sessions/.../session.jsonl --output ./recovered_session.jsonl

The recovery engine runs read-only with bounded budgets and strips locally generated review artifact identifiers before serializing clean replay state.