Skip to content

fix: request validation + OpenAI-compat correctness (#110)#117

Merged
dtzp555-max merged 1 commit into
mainfrom
fix/110-request-validation-compat
May 31, 2026
Merged

fix: request validation + OpenAI-compat correctness (#110)#117
dtzp555-max merged 1 commit into
mainfrom
fix/110-request-validation-compat

Conversation

@dtzp555-max

Copy link
Copy Markdown
Owner

Summary

Fixes the three request-path correctness/OpenAI-compat defects from the 2026-05-31 audit (#110).

  1. Non-array messages hang{"messages":"x"} passed !messages?.length then threw in messages.reduce(...); the un-awaited handler turned it into an unhandledRejection that hung the client. Now Array.isArray(messages) && length>0 → 400 invalid_request_error, before first use.
  2. Array content flattening — new contentToText() helper replaces JSON.stringify(m.content) in messagesToPrompt, extractSystemPrompt, and all promptChars char-count sites; OpenAI array content (text parts / image_url) is now flattened to text + placeholder instead of raw JSON noise. Zero JSON.stringify(m.content) remain.
  3. Streamed-error signal — a streamed upstream error after eager SSE headers emitted a bare finish_reason:"stop" + [DONE] (looks like success). Both streaming error paths (the parsed.error result branch and the non-zero-exit close-handler branch) now emit an SSE data:{"error":{...}} frame.

Error-text sanitization across emit sites is intentionally deferred to sibling issue #111 (security layer).

ALIGNMENT.md (server.mjs hard requirements)

  1. cli.js citation: N/A. OpenAI-compat shim + input-validation behaviors; OCP forwards/alters no Anthropic operation here (cli.js doesn't speak the OpenAI wire format). Justified under Rule 2.
  2. CI blacklist: no blacklisted tokens / port literals introduced; alignment.yml passes.
  3. Independent reviewer (Iron Rule 10): fresh-context opus reviewer verified the guard runs before first messages use (no bypass path), contentToText correctness incl. hoisting, no promptChars correctness regression (string counts byte-identical; stats-only sink), the error-frame fix preserves the errored/no-cache accounting, ALIGNMENT N/A agreed, and npm test148 passed, 0 failed. Verdict APPROVE WITH MINOR; both minors addressed (close-handler sibling folded in; this body carries the cli.js-N/A note).

Tests

8 new tests (contentToText 5 cases + messages-guard truth table). npm test → 148 passed, 0 failed.

Closes #110.

🤖 Generated with Claude Code

Three correctness/compat defects on the request path:

1. Non-array `messages` (e.g. {"messages":"x"}) passed the `!messages?.length`
   guard but threw in `messages.reduce(...)`; since the handler runs without
   await, the rejection silently hung the client until socket timeout. Now
   guarded with `Array.isArray(messages) && length>0` → 400 invalid_request_error,
   placed before the first use of `messages`.

2. OpenAI array `content` ([{type:"text",text},{type:"image_url",...}]) was
   JSON.stringify'd into the prompt as literal noise. New `contentToText()` helper
   flattens text parts and replaces non-text parts with a placeholder; used in
   messagesToPrompt, extractSystemPrompt, and all promptChars char-count sites
   (zero `JSON.stringify(m.content)` remain).

3. A streamed upstream error arriving after eager SSE headers emitted a bare
   finish_reason:"stop" + [DONE] — byte-identical to a successful empty completion.
   Both streaming error paths (the parsed.error result branch AND the non-zero-exit
   close-handler branch — the latter folded in per reviewer) now emit an SSE
   `data:{"error":{...}}` frame so clients can distinguish failure from empty.

Error-text sanitization across all emit sites is intentionally deferred to #110's
sibling issue #111 (security layer).

ALIGNMENT.md: these are OpenAI-compat shim + input-validation behaviors; OCP does
not forward, add, or alter any Anthropic API operation here (cli.js does not speak
the OpenAI wire format), so a cli.js citation is N/A under Rule 2. No blacklisted
tokens or port literals introduced; alignment.yml passes.

Independent fresh-context reviewer (opus): APPROVE WITH MINOR (Iron Rule 10) —
the close-handler sibling fold-in addresses MINOR #1; this commit body addresses
MINOR #2.

Closes #110.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dtzp555-max dtzp555-max merged commit 4458490 into main May 31, 2026
5 checks passed
@dtzp555-max dtzp555-max deleted the fix/110-request-validation-compat branch May 31, 2026 12:22
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.

[P2] Request validation + OpenAI-compat correctness (non-array messages hang, array content, streamed-error finish_reason)

2 participants