Skip to content

🤖 tests: fuzz gateway, provider routing, and stream error paths; fix error-serialization bugs - #4021

Merged
ThomasK33 merged 2 commits into
mainfrom
fuzz-gateway-providers
Aug 31, 2026
Merged

ThomasK33 merged 2 commits into
mainfrom
fuzz-gateway-providers

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Adds a deterministic fuzzing/chaos pass over the AI Gateway integration, provider routing/resolution, refusal-fallback logic, and stream error handling — and fixes three error-path bugs the campaign surfaced (all in how hostile provider error payloads are stringified/propagated).

Background

A faulty upstream provider recently hung/broke the agent loop until manual intervention, suggesting hidden edge cases in provider failure handling. This PR probes the mux-owned failure surfaces with seeded fuzz tests: gateway payload normalization, model-string parsing, fallback-chain resolution, route resolution, Coder gateway catalog parsing, error classification, and StreamManager's chunk-consumption loop.

Implementation

  • src/common/utils/testing/fuzzHelpers.ts: dependency-free seeded PRNG (mulberry32) plus hostile value/string generators (prototype-pollution keys, lone surrogates, cyclic objects, BigInt, throwing getters/Proxies). Every fuzz test reports its seed, so failures replay deterministically.
  • Seven *.fuzz.test.ts / chaos suites asserting totality (never throw), idempotence (normalization survives persist/reload round-trips), boundedness (fallback chains ≤ 3, deduped, acyclic; clamped errors stay clamped), and terminal settlement (every hostile stream ends in a terminal event with zero unhandled rejections and no wedged workspace; 40 seeded iterations against StreamManager with malformed deltas, unknown chunk types, mid-stream throws, truncation, and garbage usage payloads).

Fixes found by the campaign:

  1. getErrorMessage could throw despite its non-throwing contract: the String(error) fallback sat outside the try, so a hostile Proxy / throwing toString escaped — in stream-failure paths this masks the real provider error.
  2. getErrorMessage degraded cyclic/BigInt provider payloads to "[object Object]" because bare JSON.stringify threw; it now serializes with a cycle/BigInt-safe replacer so the payload's actual fields survive to the UI.
  3. StreamManager's error-chunk fallback used bare JSON.stringify(errorObj), replacing cyclic provider errors with a Converting circular structure to JSON TypeError; it now routes through getErrorMessage (regression test included).

Validation

  • Chaos harness: 40 seeded hostile chunk streams all settle terminally, leak no unhandled rejections, and leave the workspace reusable; a dedicated regression test pins the cyclic error-chunk message behavior.
  • Notable non-findings: no hangs/wedges, no unhandled rejections, no prototype-pollution vectors in alias/fallback/route lookups, and gateway model-id mappers roundtrip identity.
  • One pre-existing test updated: it pinned the old "[object Object]" degradation for circular error objects, which this PR intentionally improves.

Risks

Low. Production changes are confined to error-message serialization fallbacks (errors.ts, one StreamManager error-part branch); behavior only changes for error payloads that previously crashed serialization or degraded to "[object Object]". Affected area: error reporting in stream-failure paths. UTF-8/SSE byte-boundary decoding lives inside the AI SDK (not mux-owned), so it is intentionally out of scope; socket-level fault injection against a live gateway would need an integration harness and is noted as follow-up material.


Generated with xum • Model: anthropic:claude-fable-5 • Thinking: xhigh • Cost: $13.41

…am error paths

Deterministic seeded fuzz tests (no new deps) covering:
- gateway stream usage/finishReason normalization (hostile upstream payloads)
- model string parsing/normalization idempotence + prototype-pollution keys
- refusal-fallback chain sanitization (bounded, deduped, acyclic)
- error extraction/clamping/429 classification with hostile thrown values
- pure route resolution with garbage priorities/overrides
- Coder gateway catalog parsing and model-id mapper roundtrips
- StreamManager chaos: hostile chunk streams must settle with a terminal
  event, leak no unhandled rejections, and never wedge the workspace

Fixes found by fuzzing:
- getErrorMessage could throw despite its non-throwing contract: String(error)
  on a hostile Proxy/throwing toString escaped the guard (it runs in
  stream-failure paths where a throw masks the real provider error)
- getErrorMessage degraded cyclic/BigInt provider error payloads to
  "[object Object]" because bare JSON.stringify threw; now serializes with a
  cycle-safe replacer so the payload's actual fields survive
- StreamManager error-part fallback used bare JSON.stringify, replacing
  cyclic provider errors with a serialization TypeError; now uses
  getErrorMessage
@chatgpt-codex-connector

This comment has been minimized.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8aab9b9dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/common/utils/errors.ts Outdated
Comment thread src/node/services/streamManager.chaos.test.ts Outdated
Comment thread src/node/services/streamManager.ts
Comment thread src/node/utils/gatewayStreamNormalization.fuzz.test.ts Outdated
Comment thread src/common/constants/coderGateway.fuzz.test.ts
- getErrorMessage: track only the ancestor chain when serializing so shared
  (non-cyclic) sibling references stay intact; only true cycles become
  [Circular]
- streamManager: route tool-error payload serialization through
  getErrorMessage so cyclic/BigInt tool errors stay recoverable tool errors
- flatUsageToV3: sanitize non-finite token counts (NaN/Infinity) to undefined
  so they cannot leak into usage/cost arithmetic; fuzz asserts finiteness
- gateway model-id mappers: reject leading/trailing separators (slash and dot)
  so empty origins/model ids cannot form invalid canonical identities; fuzz
  asserts non-empty parts
- chaos test: actually start and settle a second stream on the same workspace
  to exercise the reuse path
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector

This comment has been minimized.

@ThomasK33
ThomasK33 added this pull request to the merge queue Aug 31, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 31, 2026
@ThomasK33
ThomasK33 added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit c8a8653 Aug 31, 2026
36 of 38 checks passed
@ThomasK33
ThomasK33 deleted the fuzz-gateway-providers branch August 31, 2026 17:09
@mux-bot mux-bot Bot mentioned this pull request Sep 1, 2026
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