Skip to content

refactor(sse): stop isClaudeEventPayload claiming a narrowing it never performs - #8557

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.49from
backryun:chore/ts7-types-stream-claude-predicate
Jul 26, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.49from
backryun:chore/ts7-types-stream-claude-predicate

Conversation

@backryun

Copy link
Copy Markdown
Contributor

Part of #8484. One line changed. Clears the largest cluster in open-sse/utils/stream.ts.

-function isClaudeEventPayload(payload: unknown): payload is JsonRecord {
+function isClaudeEventPayload(payload: unknown): boolean {

never did not mean unreachable — it meant the predicate was lying

Eight TS2339s read .id and .choices off never:

stream.ts(2285,38): Property 'id' does not exist on type 'never'.        x4
stream.ts(2290,53): Property 'choices' does not exist on type 'never'.   x4

never in a reachable branch is usually a signal, and it was one here:

function isClaudeEventPayload(payload: unknown): payload is JsonRecord   // L488
...
const flushedParsed = bufferedPayload as JsonRecord;                     // L2273
const isClaude = isClaudeEventPayload(flushedParsed);                    // L2277
} else if (!isClaude) {                                                  // L2283

The predicate answers "does this payload carry a Claude event type?" — a question about contents, not type. A Claude event and a non-Claude one are both JsonRecord, so payload is JsonRecord narrows nothing. Applied to an argument already typed JsonRecord, the negative branch became JsonRecord minus JsonRecord = never, and every property read in it failed.

The branch is not dead code — it is the flush-path numeric-ID normalization, and it runs.

Why boolean is the right fix, not a cast

No call site depends on the narrowing:

call site what it does with the value
stream.ts:1028 passes it to updateClaudeEmptyResponseLifecycle(…, payload: unknown)
stream.ts:2266 same
stream.ts:2277 the site in question — only reads the boolean
stream.ts:2188 passed as a callback into passthroughTailProcessor

And that last one settles it — passthroughTailProcessor.ts:27 already declares the dependency as:

isClaudeEventPayload: (payload: unknown) => boolean;

So the codebase already consumes this as a plain boolean. The change aligns the definition with the consumer that was right all along, rather than papering over the never at eight call sites.

Verification

280 → 272, zero new errors, on a line-number-agnostic diff of the full tsc error set.

  • npm run typecheck:core — clean
  • eslint — clean
  • check:file-size — the two violations it reports (providers/page.tsx, tokenHealthCheck.ts) reproduce identically on an untouched base worktree; stream.ts itself is unchanged in size
  • 338/338 across the 48 SSE/passthrough suites

A type predicate has no runtime representation, so the emitted JavaScript is byte-identical. Behavior preservation here is a property of the language, not an empirical claim.

No test added — and this time that is the finding

The last four slices each turned up an untested path that was exactly the one the new type described (#8525 the void arm, #8528 { text } documents on the response adapter, #8531 the empty → 502 arm, #8533 the ArrayBuffer backing). So I checked the same way here, expecting a fifth.

It is already covered, from both sides. tests/unit/stream-numeric-ids.test.ts:

  • "createSSEStream passthrough normalizes numeric id in final chunk without trailing newline" — the flush path with a non-Claude payload, i.e. precisely the branch TypeScript believed impossible
  • "createSSEStream Claude passthrough does not normalize numeric ids" — the other arm

Those two tests are also what make the never diagnosis provable rather than inferred: the branch TS typed as uninhabited has a passing test that executes it.

Adding anything here would be padding, so I did not.

No inline comment either

stream.ts sits at exactly its frozen cap — check:file-size counts 2887 against a frozen 2887, so a single added line fails the gate. I drafted a short doc comment, measured, and dropped it rather than spend a baseline bump on a frozen file for two lines of prose. The reasoning lives in the commit message (where git blame on this line surfaces it) and here.

Scope

stream.ts has 9 other diagnostics, in four unrelated clusters — .output/.reduce on unknown (2), a UsageTokenRecord TS2345, a TS2556 spread, and .text/.thinking on unknown (5). Different causes, left for their own slices per the group-by-kind convention. Tracked in #8484.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Verified this independently: isClaudeEventPayload's JsonRecord and the function itself are both module-private to stream.ts (no export, no external type consumer), and there's no real two-armed discriminated union here to retag (unlike #8499's ok:true/false cases) — getClaudeEventType is a content check, not a shape check, so payload is JsonRecord was never a legitimate narrowing in the first place. The consumer contract at passthroughTailProcessor.ts:27 already types this callback as returning plain boolean, which matches your fix exactly. Reproduced the never-branch failure on the base tip (stream.ts:2273-2290) and confirmed both stream-numeric-ids.test.ts guard tests pass on your branch. typecheck:core and eslint are clean, and the 2 file-size violations are pre-existing/unrelated. Looks correct and ready to merge.

One note for your own tracking: the 280->272 full-repo diagnostic delta I could not confirm within the review window, so I'm treating that number as your measurement rather than a verified one — it doesn't affect the verdict.

…r performs

Eight TS2339s in stream.ts read properties off `never`. `never` here did not
mean unreachable code — it meant a type predicate was lying.

  function isClaudeEventPayload(payload: unknown): payload is JsonRecord
  ...
  const flushedParsed = bufferedPayload as JsonRecord;
  const isClaude = isClaudeEventPayload(flushedParsed);
  } else if (!isClaude) {   // JsonRecord minus JsonRecord = never

The predicate answers "does this payload carry a Claude event type?" — a
question about contents, not type. A Claude event and a non-Claude one are both
JsonRecord, so `payload is JsonRecord` narrows nothing; applied to an argument
already typed JsonRecord it collapsed the negative branch to `never`, taking
`.id` and `.choices` with it.

Changed the return type to `boolean`. No call site depended on the narrowing:
both other callers pass the value to updateClaudeEmptyResponseLifecycle(), whose
parameter is `unknown`, and passthroughTailProcessor.ts already declares this
function as `(payload: unknown) => boolean` — so this aligns the definition with
how the codebase already consumes it.

280 -> 272, zero new, on a line-number-agnostic diff of the full tsc error set.

The diff is one line, and a type predicate has no runtime representation, so the
emitted JavaScript is unchanged.

No test added, and no gap to fill: the `never`-typed branch is real, reachable
and already covered from both sides — stream-numeric-ids.test.ts exercises
"normalizes numeric id in final chunk without trailing newline" (the flush path
with a non-Claude payload, i.e. the branch TS believed impossible) and "Claude
passthrough does not normalize numeric ids" (the other arm). 338/338 across the
48 SSE/passthrough suites.

No explanatory comment either: stream.ts sits exactly at its frozen file-size
cap (2887), so a single added line fails check:file-size. The reasoning lives
here and in the PR rather than costing a baseline bump on a frozen file.
@backryun
backryun force-pushed the chore/ts7-types-stream-claude-predicate branch from befc704 to 9bda94d Compare July 25, 2026 21:08
@diegosouzapw
diegosouzapw merged commit 11f79b6 into cheaper-inference:release/v3.8.49 Jul 26, 2026
5 checks passed
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks @backryun — merged into release/v3.8.49 via the local merge-train (validated as one combined tree: full test:unit + test:vitest 274/274 on the 32-core box, tip d4b9ce6016). Your commit keeps its authorship. 🚀

@backryun
backryun deleted the chore/ts7-types-stream-claude-predicate branch July 26, 2026 07:02
@diegosouzapw diegosouzapw mentioned this pull request Jul 28, 2026
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…r performs (cheaper-inference#8557)

Eight TS2339s in stream.ts read properties off `never`. `never` here did not
mean unreachable code — it meant a type predicate was lying.

  function isClaudeEventPayload(payload: unknown): payload is JsonRecord
  ...
  const flushedParsed = bufferedPayload as JsonRecord;
  const isClaude = isClaudeEventPayload(flushedParsed);
  } else if (!isClaude) {   // JsonRecord minus JsonRecord = never

The predicate answers "does this payload carry a Claude event type?" — a
question about contents, not type. A Claude event and a non-Claude one are both
JsonRecord, so `payload is JsonRecord` narrows nothing; applied to an argument
already typed JsonRecord it collapsed the negative branch to `never`, taking
`.id` and `.choices` with it.

Changed the return type to `boolean`. No call site depended on the narrowing:
both other callers pass the value to updateClaudeEmptyResponseLifecycle(), whose
parameter is `unknown`, and passthroughTailProcessor.ts already declares this
function as `(payload: unknown) => boolean` — so this aligns the definition with
how the codebase already consumes it.

280 -> 272, zero new, on a line-number-agnostic diff of the full tsc error set.

The diff is one line, and a type predicate has no runtime representation, so the
emitted JavaScript is unchanged.

No test added, and no gap to fill: the `never`-typed branch is real, reachable
and already covered from both sides — stream-numeric-ids.test.ts exercises
"normalizes numeric id in final chunk without trailing newline" (the flush path
with a non-Claude payload, i.e. the branch TS believed impossible) and "Claude
passthrough does not normalize numeric ids" (the other arm). 338/338 across the
48 SSE/passthrough suites.

No explanatory comment either: stream.ts sits exactly at its frozen file-size
cap (2887), so a single added line fails check:file-size. The reasoning lives
here and in the PR rather than costing a baseline bump on a frozen file.

Co-authored-by: backryun <busan011@ormbiz.co.kr>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…r performs (cheaper-inference#8557)

Eight TS2339s in stream.ts read properties off `never`. `never` here did not
mean unreachable code — it meant a type predicate was lying.

  function isClaudeEventPayload(payload: unknown): payload is JsonRecord
  ...
  const flushedParsed = bufferedPayload as JsonRecord;
  const isClaude = isClaudeEventPayload(flushedParsed);
  } else if (!isClaude) {   // JsonRecord minus JsonRecord = never

The predicate answers "does this payload carry a Claude event type?" — a
question about contents, not type. A Claude event and a non-Claude one are both
JsonRecord, so `payload is JsonRecord` narrows nothing; applied to an argument
already typed JsonRecord it collapsed the negative branch to `never`, taking
`.id` and `.choices` with it.

Changed the return type to `boolean`. No call site depended on the narrowing:
both other callers pass the value to updateClaudeEmptyResponseLifecycle(), whose
parameter is `unknown`, and passthroughTailProcessor.ts already declares this
function as `(payload: unknown) => boolean` — so this aligns the definition with
how the codebase already consumes it.

280 -> 272, zero new, on a line-number-agnostic diff of the full tsc error set.

The diff is one line, and a type predicate has no runtime representation, so the
emitted JavaScript is unchanged.

No test added, and no gap to fill: the `never`-typed branch is real, reachable
and already covered from both sides — stream-numeric-ids.test.ts exercises
"normalizes numeric id in final chunk without trailing newline" (the flush path
with a non-Claude payload, i.e. the branch TS believed impossible) and "Claude
passthrough does not normalize numeric ids" (the other arm). 338/338 across the
48 SSE/passthrough suites.

No explanatory comment either: stream.ts sits exactly at its frozen file-size
cap (2887), so a single added line fails check:file-size. The reasoning lives
here and in the PR rather than costing a baseline bump on a frozen file.

Co-authored-by: backryun <busan011@ormbiz.co.kr>
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.

2 participants