Skip to content

fix(providers): surface real Antigravity upstream error detail (#13591) - #13801

Merged
diegosouzapw merged 2 commits into
release/v3.8.51from
fix/13591-antigravity-opaque-400-error-swallowed
Sep 16, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.51from
fix/13591-antigravity-opaque-400-error-swallowed

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Refs #13591

Not covered here: the suspected root trigger (omr_skill_* schema rejections causing the
400s in the first place) is tracked separately at #13583, already fixed by #11857 on
release/v3.8.51. This PR only fixes the observability half — surfacing the real upstream
detail instead of the opaque wrapper text — which is how the owner explicitly scoped the
issue in their own comment.

Root cause (short)

Antigravity double-wraps its own errors. buildAntigravityUpstreamError()
(open-sse/executors/antigravityUpstreamError.ts) replaced error.message with the generic
"Antigravity upstream error (400)" template and buried the real Gemini-dialect detail one
level down, under upstream_details. chatCore.ts's shared failure-classification path
(providerFailure: block, both streaming and non-streaming legs) re-parses that
already-wrapped body with the generic parseUpstreamError(), which only reads the outer
error.message — so the generic text is what ends up in createErrorResult().error, the
persisted call_logs/proxy_logs, and the [ProxyEgress] antigravity status=error line.
This is why gemini (the plain API-key/OAuth provider) shows the real field-path message and
antigravity doesn't: the gemini executor never pre-wraps non-ok responses, so the raw
Google body survives untouched to the same parser.

Fix

buildAntigravityUpstreamError() now extracts the real upstream error.message from the
parsed upstream_details (when present) and composes the final error.message as
"Antigravity upstream error (<status>): <real detail>" instead of the bare templated
string. This fixes both consumers for free — the client-facing HTTP response and every
internal log/telemetry path that reads error.message — with no changes needed in
chatCore.ts or parseUpstreamError(). The existing geo-blocked branch (its own explicit,
more useful hint text) is left untouched, and a non-JSON upstream body (e.g. an HTML error
page) still falls back to the generic templated message without throwing.

Regression test

tests/unit/antigravityUpstreamError.test.ts (new file, 3 cases):

RED (on unfixed code):

✖ issue #13591: parseUpstreamError surfaces the real upstream detail for an Antigravity-wrapped 400
  AssertionError [ERR_ASSERTION]: buildAntigravityUpstreamError should surface the real upstream
  detail directly in error.message

GREEN (after the fix):

✔ issue #13591: parseUpstreamError surfaces the real upstream detail for an Antigravity-wrapped 400 (1570.882722ms)
✔ issue #13591: geo-blocked branch keeps its explicit hint message untouched (985.580498ms)
✔ issue #13591: non-JSON upstream body falls back to the generic templated message without throwing (34.035571ms)
ℹ tests 3
ℹ pass 3
ℹ fail 0

Run command: DATA_DIR=$(mktemp -d) timeout 300 node --import tsx/esm --test --test-force-exit tests/unit/antigravityUpstreamError.test.ts

Gates run

  • npx eslint --suppressions-location config/quality/eslint-suppressions.json open-sse/executors/antigravityUpstreamError.ts tests/unit/antigravityUpstreamError.test.ts — exit 0
  • npm run check:open-sse-typecheck — OK, 0 pre-existing errors
  • node scripts/check/check-file-size.mjs — OK on touched files (1 unrelated pre-existing violation in open-sse/utils/stream.ts, not touched by this PR)
  • node scripts/check/check-complexity.mjs — OK (2824 violations vs baseline 3218)
  • node scripts/check/check-cognitive-complexity.mjs — OK (1276 violations vs baseline 1437)
  • node scripts/check/check-test-discovery.mjs — OK, new test file discovered
  • Existing Antigravity error-shape tests re-run and still green:
    • tests/unit/antigravity-geoblock-resilience.test.ts (16/16 pass)
    • tests/unit/agy-gemini-400-3229.test.ts (pass)
    • tests/unit/antigravity-streaming-error-body-sanitized-2461.test.ts (pass)

Existing tests aligned

None — no existing assertion encoded the old buggy contract; all three existing test files
above exercise shapes unaffected by this change (binary/non-JSON bodies, the geo-blocked
branch, and pass-through behavior for non-Antigravity providers) and pass unmodified.

diegosouzapw and others added 2 commits September 15, 2026 18:09
Antigravity double-wraps its own errors: buildAntigravityUpstreamError()
replaced error.message with the generic "Antigravity upstream error (400)"
template and buried the real Gemini-dialect detail under upstream_details.
chatCore's shared failure path re-parses that already-wrapped body with the
generic parseUpstreamError(), which only reads the outer error.message, so
the generic text is what ends up in createErrorResult, the persisted call
logs, and the [ProxyEgress] antigravity status=error lines.

buildAntigravityUpstreamError() now composes error.message from the real
upstream detail when present, so both the HTTP response and every
log/telemetry consumer that reads error.message get the actual detail.

Regression test: tests/unit/antigravityUpstreamError.test.ts
@diegosouzapw
diegosouzapw merged commit 0ba661e into release/v3.8.51 Sep 16, 2026
19 of 21 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…souzapw#13591) (diegosouzapw#13801)

Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
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