fix(github-copilot): preserve reasoning IDs for Copilot Codex models#71684
Conversation
Greptile SummaryThis PR fixes a multi-turn failure for The core logic is sound and the tests cover all three Confidence Score: 4/5Safe to merge; the fix is narrow, well-tested, and backward compatible — only P2 style nits remain. No logic errors or security concerns found. The two findings are both P2: one is dead code in No files require special attention.
|
| const CODEX_FORWARD_COMPAT_TARGET_IDS: readonly string[] = ["gpt-5.4", "gpt-5.3-codex"]; | ||
| const CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.3-codex", "gpt-5.2-codex"] as const; |
There was a problem hiding this comment.
gpt-5.3-codex as a template is a no-op when it is itself the requested model
When resolveCopilotForwardCompatModel is called for gpt-5.3-codex, the early-return guard on lines 33-36 has already confirmed the model is not in the registry. So ctx.modelRegistry.find(PROVIDER_ID, "gpt-5.3-codex") on line 42 will always return null for that path, making "gpt-5.3-codex" the first entry in CODEX_TEMPLATE_MODEL_IDS a no-op for gpt-5.3-codex requests specifically.
The template is useful when gpt-5.4 is the target and gpt-5.3-codex is in the registry, so this isn't wrong — but a short clarifying comment would prevent future confusion about why the list includes a model that is also a target.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/github-copilot/models.ts
Line: 9-10
Comment:
**`gpt-5.3-codex` as a template is a no-op when it is itself the requested model**
When `resolveCopilotForwardCompatModel` is called for `gpt-5.3-codex`, the early-return guard on lines 33-36 has already confirmed the model is *not* in the registry. So `ctx.modelRegistry.find(PROVIDER_ID, "gpt-5.3-codex")` on line 42 will always return `null` for that path, making `"gpt-5.3-codex"` the first entry in `CODEX_TEMPLATE_MODEL_IDS` a no-op for `gpt-5.3-codex` requests specifically.
The template *is* useful when `gpt-5.4` is the target and `gpt-5.3-codex` is in the registry, so this isn't wrong — but a short clarifying comment would prevent future confusion about why the list includes a model that is also a target.
How can I resolve this? If you propose a fix, please make it concise.|
(I'm on phone rn that's why I did this small change with Claude) |
done |
dd9fbfa to
6d5eb01
Compare
|
Codex maintainer update: I rebased this PR onto current What changed:
Verification:
|
|
Codex follow-up: CI exposed an unrelated stale mock in Additional verification:
|
|
Codex follow-up 2: CI also exposed a brittle plugin-sdk fast-path fixture. The test wrote a CommonJS body to a Additional verification:
|
… support The existing guard (8fd15ed) only skipped rewriting reasoning item IDs when encrypted_content was a non-null string. When gpt-5.3-codex is used via GitHub Copilot, the model falls through to the forward-compat catch-all with reasoning:false, so encrypted_content is never requested and arrives as null — bypassing the guard and causing a rewrite. Copilot validates reasoning item IDs server-side regardless of whether the client includes encrypted_content, so the rewritten id triggers the 400 error. Two changes: 1. connection-bound-ids.ts: skip ALL reasoning items unconditionally. Reasoning items always reference server-side state bound to their original ID; rewriting any of them breaks Copilot's lookup. 2. models.ts + index.ts: extend the forward-compat cloning logic to cover gpt-5.3-codex (adds it to the template-target set and to CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for the thinking profile. Thanks @InvalidPandaa.
2793c8c to
035532d
Compare
|
Landed. Thanks @InvalidPandaa!
|
…penclaw#71684) * fix(github-copilot): preserve all reasoning IDs and add gpt-5.3-codex support The existing guard (8fd15ed) only skipped rewriting reasoning item IDs when encrypted_content was a non-null string. When gpt-5.3-codex is used via GitHub Copilot, the model falls through to the forward-compat catch-all with reasoning:false, so encrypted_content is never requested and arrives as null — bypassing the guard and causing a rewrite. Copilot validates reasoning item IDs server-side regardless of whether the client includes encrypted_content, so the rewritten id triggers the 400 error. Two changes: 1. connection-bound-ids.ts: skip ALL reasoning items unconditionally. Reasoning items always reference server-side state bound to their original ID; rewriting any of them breaks Copilot's lookup. 2. models.ts + index.ts: extend the forward-compat cloning logic to cover gpt-5.3-codex (adds it to the template-target set and to CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for the thinking profile. Thanks @InvalidPandaa. * docs(github-copilot): clarify gpt-5.3-codex is a no-op template for itself https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): remove dead reasoning prefix branch in deriveReplacementId https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): align reasoning id replay tests * test(plugin-sdk): use cjs sidecar for require fast path --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#71684) * fix(github-copilot): preserve all reasoning IDs and add gpt-5.3-codex support The existing guard (2654ced) only skipped rewriting reasoning item IDs when encrypted_content was a non-null string. When gpt-5.3-codex is used via GitHub Copilot, the model falls through to the forward-compat catch-all with reasoning:false, so encrypted_content is never requested and arrives as null — bypassing the guard and causing a rewrite. Copilot validates reasoning item IDs server-side regardless of whether the client includes encrypted_content, so the rewritten id triggers the 400 error. Two changes: 1. connection-bound-ids.ts: skip ALL reasoning items unconditionally. Reasoning items always reference server-side state bound to their original ID; rewriting any of them breaks Copilot's lookup. 2. models.ts + index.ts: extend the forward-compat cloning logic to cover gpt-5.3-codex (adds it to the template-target set and to CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for the thinking profile. Thanks @InvalidPandaa. * docs(github-copilot): clarify gpt-5.3-codex is a no-op template for itself https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): remove dead reasoning prefix branch in deriveReplacementId https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): align reasoning id replay tests * test(plugin-sdk): use cjs sidecar for require fast path --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#71684) * fix(github-copilot): preserve all reasoning IDs and add gpt-5.3-codex support The existing guard (82fbd10) only skipped rewriting reasoning item IDs when encrypted_content was a non-null string. When gpt-5.3-codex is used via GitHub Copilot, the model falls through to the forward-compat catch-all with reasoning:false, so encrypted_content is never requested and arrives as null — bypassing the guard and causing a rewrite. Copilot validates reasoning item IDs server-side regardless of whether the client includes encrypted_content, so the rewritten id triggers the 400 error. Two changes: 1. connection-bound-ids.ts: skip ALL reasoning items unconditionally. Reasoning items always reference server-side state bound to their original ID; rewriting any of them breaks Copilot's lookup. 2. models.ts + index.ts: extend the forward-compat cloning logic to cover gpt-5.3-codex (adds it to the template-target set and to CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for the thinking profile. Thanks @InvalidPandaa. * docs(github-copilot): clarify gpt-5.3-codex is a no-op template for itself https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): remove dead reasoning prefix branch in deriveReplacementId https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): align reasoning id replay tests * test(plugin-sdk): use cjs sidecar for require fast path --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#71684) * fix(github-copilot): preserve all reasoning IDs and add gpt-5.3-codex support The existing guard (26a7738) only skipped rewriting reasoning item IDs when encrypted_content was a non-null string. When gpt-5.3-codex is used via GitHub Copilot, the model falls through to the forward-compat catch-all with reasoning:false, so encrypted_content is never requested and arrives as null — bypassing the guard and causing a rewrite. Copilot validates reasoning item IDs server-side regardless of whether the client includes encrypted_content, so the rewritten id triggers the 400 error. Two changes: 1. connection-bound-ids.ts: skip ALL reasoning items unconditionally. Reasoning items always reference server-side state bound to their original ID; rewriting any of them breaks Copilot's lookup. 2. models.ts + index.ts: extend the forward-compat cloning logic to cover gpt-5.3-codex (adds it to the template-target set and to CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for the thinking profile. Thanks @InvalidPandaa. * docs(github-copilot): clarify gpt-5.3-codex is a no-op template for itself https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): remove dead reasoning prefix branch in deriveReplacementId https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): align reasoning id replay tests * test(plugin-sdk): use cjs sidecar for require fast path --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#71684) * fix(github-copilot): preserve all reasoning IDs and add gpt-5.3-codex support The existing guard (0048671) only skipped rewriting reasoning item IDs when encrypted_content was a non-null string. When gpt-5.3-codex is used via GitHub Copilot, the model falls through to the forward-compat catch-all with reasoning:false, so encrypted_content is never requested and arrives as null — bypassing the guard and causing a rewrite. Copilot validates reasoning item IDs server-side regardless of whether the client includes encrypted_content, so the rewritten id triggers the 400 error. Two changes: 1. connection-bound-ids.ts: skip ALL reasoning items unconditionally. Reasoning items always reference server-side state bound to their original ID; rewriting any of them breaks Copilot's lookup. 2. models.ts + index.ts: extend the forward-compat cloning logic to cover gpt-5.3-codex (adds it to the template-target set and to CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for the thinking profile. Thanks @InvalidPandaa. * docs(github-copilot): clarify gpt-5.3-codex is a no-op template for itself https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): remove dead reasoning prefix branch in deriveReplacementId https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): align reasoning id replay tests * test(plugin-sdk): use cjs sidecar for require fast path --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#71684) * fix(github-copilot): preserve all reasoning IDs and add gpt-5.3-codex support The existing guard (8b1a21f) only skipped rewriting reasoning item IDs when encrypted_content was a non-null string. When gpt-5.3-codex is used via GitHub Copilot, the model falls through to the forward-compat catch-all with reasoning:false, so encrypted_content is never requested and arrives as null — bypassing the guard and causing a rewrite. Copilot validates reasoning item IDs server-side regardless of whether the client includes encrypted_content, so the rewritten id triggers the 400 error. Two changes: 1. connection-bound-ids.ts: skip ALL reasoning items unconditionally. Reasoning items always reference server-side state bound to their original ID; rewriting any of them breaks Copilot's lookup. 2. models.ts + index.ts: extend the forward-compat cloning logic to cover gpt-5.3-codex (adds it to the template-target set and to CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for the thinking profile. Thanks @InvalidPandaa. * docs(github-copilot): clarify gpt-5.3-codex is a no-op template for itself https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): remove dead reasoning prefix branch in deriveReplacementId https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): align reasoning id replay tests * test(plugin-sdk): use cjs sidecar for require fast path --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#71684) * fix(github-copilot): preserve all reasoning IDs and add gpt-5.3-codex support The existing guard (32a83e9) only skipped rewriting reasoning item IDs when encrypted_content was a non-null string. When gpt-5.3-codex is used via GitHub Copilot, the model falls through to the forward-compat catch-all with reasoning:false, so encrypted_content is never requested and arrives as null — bypassing the guard and causing a rewrite. Copilot validates reasoning item IDs server-side regardless of whether the client includes encrypted_content, so the rewritten id triggers the 400 error. Two changes: 1. connection-bound-ids.ts: skip ALL reasoning items unconditionally. Reasoning items always reference server-side state bound to their original ID; rewriting any of them breaks Copilot's lookup. 2. models.ts + index.ts: extend the forward-compat cloning logic to cover gpt-5.3-codex (adds it to the template-target set and to CODEX_TEMPLATE_MODEL_IDS so it can also serve as a template source for gpt-5.4). Adds gpt-5.3-codex to COPILOT_XHIGH_MODEL_IDS for the thinking profile. Thanks @InvalidPandaa. * docs(github-copilot): clarify gpt-5.3-codex is a no-op template for itself https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): remove dead reasoning prefix branch in deriveReplacementId https://claude.ai/code/session_01EAFmq4WyKkiUkVAqRXp4Bm * fix(github-copilot): align reasoning id replay tests * test(plugin-sdk): use cjs sidecar for require fast path --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
Summary
gpt-5.3-codexvia GitHub Copilot fails withLLM request failed: provider rejected the request schema or tool payloadon every request after the first reasoning turn. The raw provider error is400 The encrypted content for item rs_2a72bff773173087 could not be verified. Reason: Encrypted content item_id did not match the target item id.gpt-5.3-codex(or future Codex models) through GitHub Copilot cannot have multi-turn conversations — every second message hard-fails.connection-bound-ids.ts— skip alltype: "reasoning"items during ID rewriting, not just those with a non-nullencrypted_contentstring. (2)models.ts— addgpt-5.3-codexas a forward-compat clone target and update the catch-all regex to mark Codex-named model IDs asreasoning: true. (3)index.ts— addgpt-5.3-codextoCOPILOT_XHIGH_MODEL_IDS.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
gpt-5.3-codexis not in the Copilot model registry and falls through toresolveCopilotForwardCompatModel's catch-all withreasoning: false. Withreasoning: false, pi-ai never includesinclude: ["reasoning.encrypted_content"]in the request, so Copilot returns reasoning items withencrypted_content: null. The guard added in fix(github-copilot): preserve reasoning ids with encrypted_content #71448 —typeof item.encrypted_content === "string"— evaluates tofalsefornull, so the connection-bound item ID is still rewritten tors_<hash>. On the next turn, Copilot looks up its server-side reasoning state by item ID, finds the original connection-bound ID but receivesrs_<hash>, and returns 400.encrypted_contentstring check in fix(github-copilot): preserve reasoning ids with encrypted_content #71448 was too narrow. Copilot validates reasoning item IDs server-side regardless of whether the client holds theencrypted_contentblob.Regression Test Plan (if applicable)
extensions/github-copilot/connection-bound-ids.test.ts,extensions/github-copilot/models.test.tsencrypted_content: nulland reasoning items with noencrypted_contentfield must not have their IDs rewritten; Codex-named model IDs must resolve withreasoning: truefrom the catch-all."preserves reasoning IDs when encrypted_content is present"— updated here to a single test covering all three cases (string, null, absent).User-visible / Behavior Changes
Multi-turn conversations with
gpt-5.3-codex(and any future*-codexmodel) via GitHub Copilot no longer fail after the first reasoning turn. No config or defaults change.Diagram (if applicable)
Security Impact (required)
Repro + Verification
Environment
gpt-5.3-codex/github-copilotagents.defaults.models: ["github-copilot/gpt-5.3-codex"]Steps
gpt-5.3-codexExpected
Actual
LLM request failed: provider rejected the request schema or tool payload.400 The encrypted content for item rs_2a72bff773173087 could not be verified.Evidence
Gateway logs showing the error (before fix):
Live test passing after fix:
Human Verification (required)
gpt-5.3-codex; unit tests for all threeencrypted_contentstates (string / null / absent) and forcodex-pattern model IDs in the catch-all.gpt-5.4template cloning still works;gpt-5.3-codexprefers itself as template source overgpt-5.2-codexwhen both are in registry; non-reasoning models (gpt-5.4-mini,claude-sonnet-4.6) remain unaffected.Review Conversations
Compatibility / Migration
Risks and Mitigations