Blocking stop hook continuation can fail with invalid local message id

Open 💬 15 comments Opened May 2, 2026 by shawnsw

Bug

A blocking stop hook can break the Codex turn instead of prompting Codex to fix the blocked condition.

Observed failure:

{
  "error": {
    "message": "Invalid 'input[118].id': 'c047686e-8eaf-4c21-9eba-e25d3122d898'. Expected an ID that begins with 'msg'.",
    "type": "invalid_request_error",
    "param": "input[118].id",
    "code": "invalid_value"
  }
}

Why this matters

A stop hook with decision: "block" is supposed to inject a continuation prompt, for example telling Codex to fix failing lint/type/test checks before stopping.

In this failure mode, the stop hook successfully blocks and emits the continuation prompt, but the next Responses API request rejects the conversation input. That makes the hook mechanism counterproductive: it prevents Codex from continuing to fix the problem.

Expected behavior

When a stop hook blocks, Codex should append the hook continuation prompt and continue the turn normally.

Actual behavior

Codex sends a locally generated UUID as input[].id for a message item. The Responses API rejects it because message ids must be server-generated msg... ids.

Root-cause hypothesis

build_hook_prompt_message creates a user message with a local UUID. Later, attach_item_ids reattaches hidden item ids into the serialized Responses API request body.

That path appears to treat local message UUIDs the same as server-generated message ids, so a local UUID is sent as a wire message.id.

Reference fix

I prepared a minimal fix in a fork.

Reference commit:
https://github.com/shawnsw/codex/commit/15286e639ad4f11771ddf1393b565f503c0aafc7

Approach:

  • only reattach message.id when it looks like a server-generated msg... id
  • continue preserving ids for other Responses API output item types
  • add regression coverage for local UUIDs and server message ids

View original on GitHub ↗

15 Comments

kaydash · 2 months ago

저도 이 오류를 계속 겪습니다. ms azure ai foundry에 배포된 gpt-5.5 모델은 response api로만 서빙되며, 이것을 연결해서 사용하는중에 자주 발생하고있어요. opencode + omo 에서는 이런오류를 겪지않는데말이죠.

shawnsw · 2 months ago
저도 이 오류를 계속 겪습니다. ms azure ai foundry에 배포된 gpt-5.5 모델은 response api로만 서빙되며, 이것을 연결해서 사용하는중에 자주 발생하고있어요. opencode + omo 에서는 이런오류를 겪지않는데말이죠.

Similar situation, I'm also using Azure AI Foundry, model gpt-5.5. Unsure if this is only reproducible with Foundry.

oxysoft · 2 months ago

Indexed this hook ticket in the umbrella tracker: #21753

Goal: collect the scattered Codex hook requests and bugs into one parity matrix for Full Claude Code Hook Parity (29+), while preserving this issue as the detailed thread for its specific behavior.

AdithyanI · 1 month ago
> 저도 이 오류를 계속 겪습니다. ms azure ai foundry에 배포된 gpt-5.5 모델은 response api로만 서빙되며, 이것을 연결해서 사용하는중에 자주 발생하고있어요. opencode + omo 에서는 이런오류를 겪지않는데말이죠. Similar situation, I'm also using Azure AI Foundry, model gpt-5.5. Unsure if this is only reproducible with Foundry.

I confirm I see the same issue when using the model from Azure AI Foundry.

shawnsw · 1 month ago
> > 저도 이 오류를 계속 겪습니다. ms azure ai foundry에 배포된 gpt-5.5 모델은 response api로만 서빙되며, 이것을 연결해서 사용하는중에 자주 발생하고있어요. opencode + omo 에서는 이런오류를 겪지않는데말이죠. > > > Similar situation, I'm also using Azure AI Foundry, model gpt-5.5. Unsure if this is only reproducible with Foundry. I confirm I see the same issue when using the model from Azure AI Foundry.

Do you see the same with direct OpenAI model API? Haven't tested that myself.

shawnsw · 13 days ago

I believe this has the same root cause as #27928: a local Codex-generated message ID is replayed into input[].id, and Azure Responses rejects it because message IDs must begin with msg.

I verified a local patch that strips non-msg* IDs only for ResponseItem::Message before Responses API requests, while preserving server message IDs and non-message item IDs.

More details in #27928:
https://github.com/openai/codex/issues/27928#issuecomment-4817066485

My verified branch:
https://github.com/shawnsw/codex/tree/fix/responses-local-message-ids

@pakrym-oai this fix addresses both #20783 and #27928. Refrained from raising a PR per the repo guidelines, but happy to raise one if it would be accepted?

doudouOUC · 10 days ago

Additional local reproduction on Codex Desktop 26.707.31428 (CLI 0.144.0-alpha.4):

  • A Stop hook injected a user hook_prompt after a turn that edited files.
  • The local rollout persisted that prompt as a Responses message with a locally generated UUID in message.id, rather than a server-generated msg... ID.
  • A later compaction retained the same UUID in replacement_history.
  • After resuming the thread, every follow-up failed before model output with ApiIdParam / invalid_id_prefix because that local UUID was serialized as input[6].id.

The UUID is not the thread/conversation ID and the user did not supply it through an API request. This reproduces the failure from an ordinary Desktop session with a Stop hook enabled.

This supports guarding both persistence/replay paths: do not retain a local UUID as a wire message.id; and only attach a message ID to a Responses request when it is a server-generated msg... ID. No local paths, thread IDs, or archived conversation contents are included here.

jaymcampbell · 9 days ago

Confirming this on the direct OpenAI path (stock provider config, ChatGPT-account auth — no Azure involved): a Stop hook returning {"decision":"block","reason":"…"} permanently wedged a session in the Codex macOS app.

What the session rollout shows: the block reason was recorded into history as a user message item wrapped in <hook_prompt hook_run_id="stop:2:…">, carrying a locally generated bare UUIDv7 id (019f4da5-72da-7671-b599-b3cd3b157b40 — no type prefix). Every subsequent request replayed that item and was rejected:

Invalid 'input[0].id': '019f4da5-72da-7671-b599-b3cd3b157b40'. Expected an ID that begins with 'msg'.

We saw the same rejection hours apart (input[0].id, later input[97].id) as the conversation grew. The session never recovered and had to be abandoned — so beyond breaking the continuation turn (the OP's case), a Stop-hook block poisons persisted history: every later turn fails, which in practice is permanent session loss. Notably it is not deterministic: another long-running session on the same machine absorbed five identical stop hook injections (same <hook_prompt> shape, same bare-UUIDv7 ids in the rollout) without failing, including across compaction and resume — so the rejection appears to depend on when/whether the locally-generated id gets reattached on replay. We've disabled decision:block on Codex entirely until this is fixed.

Environment: macOS (arm64, Darwin 25.5.0), Codex macOS app; codex --version on the same machine reports codex-cli 0.131.0; model gpt-5.6-terra. #32282 looks like another manifestation of the same class.

null0526 · 7 days ago

I can reproduce this in Codex Desktop on Windows, and in my case it is also triggered by a hook.

Hook feedback

[ApiIdParam] [input[0].id] [invalid_id_prefix]
Invalid 'input[0].id': '019f5eaa-4fa9-70d1-9d35-a6b2fbdcae81'.
Expected an ID that begins with 'msg'.

The rejected value is a locally generated UUID from the hook-generated message, not a server-generated msg... ID.

This appears consistent with the root cause described in this issue: a hook continuation message is persisted with a local UUID, and that UUID is later reattached as a Responses API input[].id.

Environment:

Product: Codex Desktop
Platform: Windows x64
Trigger: hook feedback / hook continuation
Authentication: ChatGPT subscription

I have not verified a fix or workaround yet. This report only confirms another reproduction of the same failure path.

sh-inv · 6 days ago

Additional reproduction after the latest macOS desktop update:

  • Product: Codex Desktop for macOS
  • App version: 26.707.71524, build 5263
  • Bundled CLI: codex-cli 0.144.2
  • Trigger: global Stop hook returning {"decision":"block","reason":"..."}
  • Loop guard: when stop_hook_active is true, the hook returns {"continue":true}
  • Result: the generated continuation fails with ApiIdParam / invalid_id_prefix because a local UUID is sent as input[].id where a server-generated msg... ID is required
  • Scope: reproduced in fresh threads because the Stop hook is global
  • Timing: this hook worked before the desktop update and began failing after the update

The same invalid-ID class also appears in local desktop logs during ambient_suggestions refresh, which suggests the item-ID serialization/replay problem is broader than only parsing the Stop hook result. The blocking Stop hook reliably exposes it by creating a continuation message.

No local paths, thread IDs, full UUIDs, or conversation contents are included here.

drastawi · 6 days ago

Verified on 2026-07-14 against current main d7ba5ff9553a6aa0898a8e3bd5cb3bc00d0c9ddf and parent 08ba14b03d0 of #32312. A focused Azure/store request test added two hook-prompt user messages with the same synthetic bare UUID, representing persisted history and compaction replacement_history. At the parent, both IDs remained on the outbound request and the omission assertions failed (0 passed, 1 failed). On current main, the same test passes (1 passed, 2978 skipped): request preparation removes any response-item ID that is not prefixed before the store/item-IDs early return, covering legacy history on HTTP and WebSocket paths.

This confirms that the serializer/replay root cause reported here is resolved in source by #32312 (merge c9d52de5ca52a6b4439e7c0f69b34f6331926bb4); a duplicate patch or report is not needed. The installed Codex Desktop 26.707.71524 still bundles codex-cli 0.144.2 and reproduced the bare-UUID hook-prompt poison in a disposable task today. The remaining upstream delivery gap is a Desktop build that contains c9d52de5. Standalone CLI 0.144.4 resume can process the poisoned disposable history, but that does not repair the older Desktop app-server loaded context.

tadeumaia · 6 days ago

Same problem been happening to be , breaking a lot of long threads

019f6020-9870-7130-b4a7-19d4bfd377d7

yeahjack · 6 days ago

Additional affected Codex Desktop session ID: 019f12d2-590c-7941-9c82-73869d5c7357.

Vyzerythe · 6 days ago

Confirming this is now reproducible in the new ChatGPT desktop app's Codex mode with a configured hook.

Observed behavior

  • The hook workflow was working normally through July 13, 2026.
  • Beginning overnight July 13–14 (America/New_York), when the hook fires the Desktop thread fails with:
[ApiIdParam] [input[0].id] [invalid_id_prefix] Invalid 'input[0].id': '019f611a-958f-7621-88bb-eede86b646d4'. Expected an ID that begins with 'msg'.
  • The affected thread is effectively unusable in Desktop after this occurs; subsequent attempts cannot continue normally.
  • The same thread can be resumed through Codex CLI when hooks are disabled for that invocation, which points to the Desktop hook persistence/replay path rather than corruption of the underlying task.

This appears consistent with the Desktop regression tracked in #32282, but the hook firing is the direct trigger in this reproduction. Because hooks are part of the normal workflow, this is currently blocking practical use of the Desktop app rather than being an intermittent cosmetic error.

App/OS version and a sanitized hook definition can be added if maintainers identify the preferred diagnostics or log location.

jisi71 · 6 days ago

I reproduced this issue twice in Codex Desktop on macOS.

Environment:

  • Codex Desktop on macOS arm64
  • codex-cli 0.136.0
  • Project-level Stop hook configured through .codex/hooks.json
  • OpenAI Responses API

The Stop hook returned a blocking decision:

{"decision":"block","reason":"Code files changed but review is still required..."}

Codex then stored the generated hook prompt as a local user message with a UUID-style id:

{"type":"message","role":"user","id":"<local-uuid>","content":[{"type":"input_text","text":"<hook_prompt ...>...</hook_prompt>"}]}

A later request failed with:

Invalid 'input[57].id': '<local-uuid>'. Expected an ID that begins with 'msg'.

After this happens, every subsequent prompt in the same thread returns the same error, making the thread permanently unusable. A structured scan of local rollout logs found multiple Stop-hook-generated hook_prompt messages whose message ids were UUIDs rather than server-generated msg_ ids.

As a project-side workaround, I changed the Stop hook from decision:block to advisory decision:allow while preserving the review-required marker. I added a regression test that creates an isolated Git repository, triggers the real review marker and Stop hook, and verifies that the marker remains while the Stop decision is allow. The failure has not reproduced through that path since.

This appears to be the same local-message-id / attach_item_ids root cause described here and is also related to #27928.