Skip to content

bin proxies: a JSON null body crashes seven chat proxies — parse succeeds, first deref throws, process exits 1 (clef and opencode-route already 400) #2816

Closed

Description

@dezren39

Summary

Seven of the nine bin/*-proxy.mjs chat proxies crash the whole process on a
POST /v1/chat/completions whose body is valid JSON but not an object — the
literal null. Each handler JSON.parses the body inside a try/catch (so
malformed JSON correctly gets a 400), but then dereferences the parsed value
without checking it is an object, so null throws a TypeError outside any
guard and Node exits. One malformed request from any client therefore kills a
long-lived fleet proxy until its supervisor restarts it.

The other two proxies already do this right and are the in-repo pattern to copy:

  • bin/clef-proxy.mjs:211 — if (!body || typeof body !== 'object') → 400
    "Invalid JSON body: expected an object", process stays up.
  • bin/opencode-route-proxy.mjs:709 — if (!isPlainObject(body)) → 400
    "request body must be a JSON object", process stays up.

Evidence (main c90fcbc7, verified this run)

First unguarded dereference after JSON.parse in each affected handler:

proxy first deref
bin/design-code-proxy.mjs dispatchWithFallback reads body.model at :247 (called from :236)
bin/codex-proxy.mjs Boolean(body.stream) at :1088
bin/claude-code-proxy.mjs Array.isArray(body.messages) at :723
bin/agy-proxy.mjs Boolean(body.stream) at :847
bin/antigravity-ls-proxy.mjs Boolean(body.stream) at :770
bin/commandcode-proxy.mjs Boolean(body.stream) at :903
bin/muse-proxy.mjs Boolean(body.stream) at :1601

Probe (laptop, real modules, files md5-identical to main; probe dir
~/git/fleet/probe-2026-10-30-laneB-nullbody/): each proxy was spawned on a
scratch port, given a malformed-JSON control ({bad json → HTTP 400, process
alive in all nine), then POST /v1/chat/completions with body null:

  • All seven above: fetch failed with UND_ERR_SOCKET, process exited with
    code 1
    (uncaught TypeError stack naming the deref line above).
  • Controls: clef-proxy (POST /v1/systemone) and opencode-route-proxy both
    answered HTTP 400 and stayed alive.

Secondary (code-read, not probed): non-object scalars/arrays take inconsistent
paths instead of a uniform 400 — e.g. an array body reaches design-code's
dispatch with body.model === undefined, and resolveModelConfig
(bin/design-code-proxy.mjs, requestedModel || 'design-code-pro') silently
substitutes the default model for a body that was never a chat request.

Why it matters

These proxies are long-lived fleet infrastructure (supervised, shared by many
sessions). A single stray null body — a buggy client, a health-check tool, a
hand-rolled curl — takes the proxy down for every routed session, and the
failure mode (process exit, socket reset) points at the proxy rather than at
the bad request, so it is hard to attribute after the fact. The two guarded
siblings prove the intended contract: a non-object body is a client error
(400), never a process exit.

Acceptance criteria

  • Every bin/*-proxy.mjs JSON POST handler (chat completions at minimum;
    audit the other JSON POST routes in the same files) rejects a parsed
    body that is not a plain object with that proxy's existing 400 error
    shape, before any property dereference.
  • null, array, string, and number bodies all get the 400; the process
    stays alive and answers a subsequent valid request.
  • Each affected proxy's bin/*.test.mjs gains a regression test in the
    existing harness style: non-object body → 400 and the proxy still
    serves afterwards (the clef / opencode-route behaviour is the oracle).
  • No behaviour change for object bodies, malformed JSON (still 400 from
    the parse catch), or the guarded clef / opencode-route proxies.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    effort/sSmall: expected under 2 hours of work
    app/codex-proxybin/codex-proxy.mjs fleet proxy
    app/agy-proxybin/agy-proxy.mjs warm-session proxy
    app/antigravity-ls-proxyPackage/app: antigravity-ls-proxy (bin/ in this repo)
    app/muse-proxybin/muse-proxy.mjs OpenAI-compatible Muse bridge
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator (new-issue-creator-code-b)
    • Session / ID: cron job new-issue-creator-code-b, run 2026-10-30 14:20 CDT
    • Found via: filed this run
    • Task: Triage the non-object JSON body crash across the seven unguarded bin chat proxies and route a fix packet (guard shape per proxy is triage's pin; clef-proxy:211 and opencode-route-proxy:709 are the in-repo oracles)
    • Expectation: effort s — done when null/array/string/number bodies get each proxy's 400 shape with the process staying alive, regression tests land in each proxy's test harness, and object-body behaviour is unchanged
    • Links: evidence probe on laptop ~/git/fleet/probe-2026-10-30-laneB-nullbody/ (real modules, md5-matched to main c90fcbc; malformed-JSON controls 400/alive, null -> exit 1 for all seven, guarded controls 400/alive)
  3. 12 remaining items

  4. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  5. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  6. added a commit that references this issue on Nov 1, 2026
  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

    Implemented: inline guard after each chat-completions JSON.parse (design-code :192, codex :1086, claude-code :731, agy :860, antigravity-ls :763, commandcode :909, muse :1594) — non-plain-object body → that proxy's existing 400 shape, before any deref. Audit: one request-body JSON POST route per file; muse /v1/responses already guards via aliasResponsesRequestBody.

    Tests (laptop): new per-proxy regression tests 7/7 pass (null/array/string/number → 400, proxy still serves). Full suites: design-code 11/11, claude-code 6/6, agy 12/12, antigravity-ls 13/13, commandcode 3/3, muse 25/25; codex 20 pass / 0 fail in a 20-min run cut by the harness timeout (pre-existing soak duration; details on the PR). Codex review on the PR: completed, no findings.

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

Metadata

Metadata

Assignees

Labels

app/agy-proxybin/agy-proxy.mjs warm-session proxyapp/antigravity-ls-proxyPackage/app: antigravity-ls-proxy (bin/ in this repo)app/claude-code-proxybin/claude-code-proxy.mjsapp/codex-proxybin/codex-proxy.mjs fleet proxyapp/commandcode-proxybin/commandcode-proxy.mjs fleet proxyapp/design-code-proxybin/design-code-proxy.mjs fleet proxyapp/muse-proxybin/muse-proxy.mjs OpenAI-compatible Muse bridgeeffort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions