Skip to content

fix(gateway): accept rotating Copilot Responses IDs - #1959

Merged
BYK merged 5 commits into
mainfrom
fix/issue-1788-remaining-failure
Sep 30, 2026
Merged

BYK merged 5 commits into
mainfrom
fix/issue-1788-remaining-failure

Conversation

@BYK

@BYK BYK commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Pin GitHub Copilot's rotating Responses lifecycle IDs only at the canonical Copilot Responses endpoint; keep explicit provider routing and other providers strict.
  • Preserve recall continuation, worker, buffered, passthrough, and provisional behavior while rejecting malformed lifecycle snapshots and keeping hidden recall data private.
  • Bound emitted SSE bytes, keep sequence numbers consecutive after rejected frames, and project reviewed fields into rebuilt terminals.

Addresses #1788. The latest report shows a Copilot Responses lifecycle failure; it does not establish that the earlier Gemini 400 shares this cause.

Verification

  • Gateway affected suites: 986 passed.
  • Full Vitest: 11,706 passed, 229 skipped, 2 failed. Both failures (remote management 200 vs 404 in dashboard-api and knowledge-api) reproduce on the base branch; no changed-path failure.
  • UI suite run separately: 576 passed (the failed Vitest phase prevented pnpm test from reaching it).
  • Typecheck, lint, format: passed; lint reports existing warnings.
  • Failing-first base regression, guard-removal mutation checks, and ten-run stability checks passed. Independent correctness and security reviews of the exact commit both returned MERGE.

Devin Review

Comment thread packages/gateway/src/pipeline.ts Outdated
Comment on lines +10561 to +10568
event === "response.failed"
) {
const response = parsed.response as Record<string, unknown> | undefined;
if (acc.id && response?.id !== acc.id) {
const requireCompleteLifecycle =
opts.validation !== "codex" || opts.pinResponseId === true;
if (
!response ||
typeof response.id !== "string" ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The identity check for terminal response events incorrectly ignores the requireCompleteLifecycle flag, leading to overly strict validation for codex streams.
Severity: MEDIUM

Suggested Fix

Update the identity check for terminal events to be conditional on the requireCompleteLifecycle flag. When the flag is true, enforce the strict response.id === acc.id check. When false, apply a more lenient check, such as only comparing IDs if both acc.id and response.id are defined.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/gateway/src/pipeline.ts#L10561-L10568

Potential issue: The `requireCompleteLifecycle` variable is defined to control
validation strictness but is not used in the identity check for terminal events like
`response.completed` and `response.failed`. This causes an overly strict validation
where the code unconditionally checks if `response.id` matches `acc.id`. For streams
where a complete lifecycle is not required (e.g., `codex` validation without
`pinResponseId`), this can incorrectly throw a "Responses terminal event changed
response identity" error.

Also affects:

  • packages/gateway/src/pipeline.ts:12525~12543

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in f9704d5. Unpinned Codex terminal events may omit a repeated ID; an explicit mismatch still fails, and public/pinned lifecycles remain strict. The second terminal-output check now follows the same rule. Regressions cover sparse principal and recall continuation streams, mismatched IDs, and missing pinned IDs; the full test suite passes.

@devin-ai-integration devin-ai-integration Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Results 📊

✅ Patch coverage is 93.92% (170 of 181 changed executable lines covered; target 80%).
Project statement coverage is 85.05% (up 0.04 percentage points from base (e0aba55) to head (e40f7b2)).

Changed files with executable lines (5)
File Patch coverage Changed executable lines
packages/gateway/src/pipeline.ts 97.03% 98/101 covered; missed: 6853, 6870, 10499; partial branches: 1111, 9308, 10498, 10827, 12140
packages/gateway/src/stream/openai-responses.ts 94.74% 54/57 covered; missed: 2702, 2708, 2709; partial branches: 639, 2659, 2757
packages/gateway/src/translate/openai.ts 73.33% 11/15 covered; missed: 778, 783, 798, 803; partial branches: 771
packages/gateway/src/llm-adapter.ts 83.33% 5/6 covered; missed: 3067
packages/gateway/src/translate/openai-responses.ts 100.00% 2/2 covered
Coverage diff
@@            Coverage Diff             @@
##          main     #1959       +/-##
==========================================
+ Coverage    85.01%    85.05%    +0.04%
==========================================
  Files          318       318         —
  Tracked lines     49482     49619      +137
  Branches     40478     40709      +231
==========================================
+ Hits         42061     42199      +138
+ Misses        7421      7420        -1
- Partials      4285      4295       +10

Generated by Coverage Action

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

🐛 1 issue in files not directly in the diff

🐛 Sparse principal terminal loses response identity

When a Codex principal terminal omits its ID, validateResponseLifecycle accepts it, but the no-recall path forwards it unchanged. Clients receive a terminal without the ID established by response.created.

Devin Review

@BYK

BYK commented Sep 30, 2026

Copy link
Copy Markdown
Owner Author

Reviewed Devin’s sparse-terminal finding: the observation is correct, but it is not a gateway regression. Unpinned Codex permits a terminal that omits repeated id/model after a validated response.created (the same sparse contract used by the normalizer). On the no-recall path, the gateway deliberately forwards the accepted upstream terminal unchanged; adding fields would change the provider’s wire payload. The internal completed response retains the created ID/model and output, which the new principal test asserts. Recall continuations rebuild the terminal with that principal ID/model and output, also asserted. Public and pinned streams still require a complete terminal identity, and supplied mismatched IDs fail closed.

@BYK
BYK merged commit 5a05326 into main Sep 30, 2026
36 checks passed
@BYK
BYK deleted the fix/issue-1788-remaining-failure branch September 30, 2026 16:58
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-30 16:59 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant