fix(session): normalize BigInt tool results before persistence

AN
3h ago
+334-27

Issue for this PR

Closes #1486

Stacked on feat/analyst-harness-dab-2026-10 (#1441)

Type of change

  • Bug fix
  • New feature
  • Refactor / code cleanup
  • Documentation
  • Other

What does this PR do?

A tool result whose metadata contains a native BigInt (for example a large warehouse integer) crashed the session when the completed tool part was stored.

Root cause: the metadata schema accepts nested BigInts, but parts are written to a JSON column via plain JSON.stringify, which throws on BigInt. Both the legacy Session.updatePart and the Effect-service updatePart (event projector) had the same exposure. Outgoing-request and tracing paths were already protected.

Before: persisting the completed part threw JSON.stringify cannot serialize BigInt and the turn failed before the next model call. After: parts are normalized once before DB write, event publication and return. Safe-range BigInts become numbers; larger positive/negative values become exact decimal strings. Input objects are not mutated; existing request/tracing defaults are unchanged.

Deferred: class instances and typed arrays (e.g. BigInt64Array) inside plugin-tool metadata are still skipped by the normalizer (review P2); cyclic/custom-class metadata handling is unchanged. Current native SQL output is already BigInt-safe, so no SQL formatter change.

How did you verify your code works?

  • bun test (json-bigint, session, tool-result-bigint, sql-execute-bigint-metadata, tracing-bigint): 23 pass, 0 fail; regressions fail on base with the exact BigInt error
  • bun test test/session: 1191 pass, 0 fail
  • Server consumers of updatePart (httpapi-session, session-messages, session-transcript, negative-tokens-regression, httpapi-sdk): 43 pass, 0 fail
  • bun run typecheck: pass; git diff --check: pass
  • Marker audit against the last merged v1 tag (v1.17.9): session/session.ts exists there, changes wrapped in altimate_change markers; session/index.ts already carries markers and the new blocks are wrapped. The standard --markers run is not meaningful because upstream main is v2.
  • Independent Codex review: no P0/P1; one P2 (above), deferred.

Screenshots / recordings

N/A

Checklist

  • Tests added
  • Typecheck passes
  • Upstream-shared changes wrapped in altimate_change markers

🤖 Generated with Claude Code


[!NOTE]
Medium Risk
Changes the session part persistence and event path used by all tool updates; behavior is narrow (BigInt coercion only) but sits on a critical turn-completion path.

Overview
Fixes session turns failing when tool parts (e.g. SQL metadata) contain native BigInt values, which previously broke JSON.stringify on the part JSON column and aborted the turn before the next model call.

normalizeBigInts now accepts an optional safe-number mode: in-range integers become JavaScript numbers; larger values stay exact decimal strings. Default string behavior for LLM request paths is unchanged.

Normalization runs at the shared updatePart boundary in both the legacy DB path (persistPart in session/index.ts) and the Effect session service (session/session.ts), so stored rows, PartUpdated events, and returned parts are JSON-safe without mutating caller-owned objects.

Tests cover the helper rules, Effect updatePart for running/completed tool states, and a live flow with four parallel scratch SQL tools through persistence, events, and tracing.

Reviewed by Cursor Bugbot for commit 464ac2b2d7c6597a1190ad941c5e3dfe4fa978b6. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes session crashes when tool results carry native BigInt metadata, which previously threw JSON.stringify cannot serialize BigInt when persisting completed tool parts. Both the legacy Session.updatePart and the Effect-service updatePart now normalize parts once before DB write, event publish, and return.

  • Added a safe-number mode to normalizeBigInts: safe-range BigInts become numbers; larger values become exact decimal strings; existing string default and request/tracing paths unchanged.
  • Input objects are no longer mutated; class instances and typed arrays in plugin-tool metadata stay deferred.
  • Added regression tests, including a real prompt loop with four concurrent sql_execute calls carrying nested BigInt metadata.
  • Closes #1486.