Skip to content

fix: cap journal body at 64KB to prevent Invalid string length crash on GET /__aimock/journal#308

Merged
jpr5 merged 2 commits into
mainfrom
fix/journal-body-cap
Jul 17, 2026
Merged

fix: cap journal body at 64KB to prevent Invalid string length crash on GET /__aimock/journal#308
jpr5 merged 2 commits into
mainfrom
fix/journal-body-cap

Conversation

@jpr5

@jpr5 jpr5 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Problem

GET /__aimock/journal serializes the full journal (up to 1000 entries) via JSON.stringify. When LLM request bodies are large (~530 KB each in prod-like agentic traffic), the aggregate JSON string exceeds V8's 512 MB limit → RangeError: Invalid string length. The catch-all logged only err.message, so no stack ever appeared in prod logs.

Real stack (locally reproduced):

[aimock] [DIAG] Unhandled RangeError on GET /__aimock/journal:
RangeError: Invalid string length
    at JSON.stringify (<anonymous>)
    at handleControlAPI (file:///Users/jpr5/dev/aimock/dist/server.js:177:16)
    at handleHttpRequest (file:///Users/jpr5/dev/aimock/dist/server.js:822:10)
    at Server.<anonymous> (file:///Users/jpr5/dev/aimock/dist/server.js:778:3)
    at Server.emit (node:events:508:20)
    at parserOnIncoming (node:_http_server:1216:12)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:125:17)
[aimock] Unhandled request error: Invalid string length

Fix

Cap the retained request-body size at 64 KB per journal entry at the journal source (journal.add()). Bodies exceeding the cap are replaced with a truncation marker { "__aimock_truncated": true, "originalByteSize": N, "note": "..." }. This bounds aggregate journal memory to ~64 MB at maxEntries=1000 (well under 256 MB), and protects all downstream consumers of journal.getAll(), not just /journal.

Also unifies the catch-all error log to include err.stack unconditionally for production observability (removes [DIAG] debug tag from the diagnosis branch).

RED (pre-fix, crash confirmed locally)

$ curl -s http://127.0.0.1:4010/__aimock/journal > /tmp/response.txt
$ wc -c /tmp/response.txt
       0 /tmp/response.txt   # empty — crash returned 500

$ grep -A 10 "RangeError" /tmp/aimock-fix-red.log
[aimock] [DIAG] Unhandled RangeError on GET /__aimock/journal:
RangeError: Invalid string length
    at JSON.stringify (<anonymous>)
    at handleControlAPI (file:///Users/jpr5/dev/aimock/dist/server.js:177:16)
    at handleHttpRequest (file:///Users/jpr5/dev/aimock/dist/server.js:822:10)
[aimock] Unhandled request error: Invalid string length

Repro: 1000 × 530 KB POST bodies → GET /__aimock/journalRangeError: Invalid string length

GREEN (post-fix, no crash)

$ curl -s http://127.0.0.1:4010/__aimock/journal | wc -c
  520001   # 520 KB of valid JSON — NOT empty

$ curl -s http://127.0.0.1:4010/__aimock/journal | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'entries={len(d)}, ok')"
entries=1000, ok

$ grep "RangeError\|Invalid string length" /tmp/aimock-fix-green.log | wc -l
       0   # no errors

# Truncation markers present in entries:
# entry[0]: __aimock_truncated=True, originalByteSize=542854
# entry[1]: __aimock_truncated=True, originalByteSize=542854
# entry[2]: __aimock_truncated=True, originalByteSize=542854
# Total truncated: 1000/1000

Regression test

Added to src/__tests__/journal.test.ts"journal body cap (ISL regression)" describe block with 3 tests:

  1. replaces oversized bodies with a truncation marker — 100 KB body → marker stored
  2. retains bodies that are within the 64 KB cap — small body → stored as-is
  3. JSON.stringify(journal.getAll()) does not throw with 1000 large-body entries — 1000 × 100 KB bodies, must not throw

Pre-push checks

  • format: pass
  • lint: pass
  • tests: 4548 passed (154 test files)
  • build: pass

⚠️ Draft — do not merge. aimock is deployed to prod on Railway. A human must authorize merge + deploy.

…/__aimock/journal

When journal entries retain large LLM request bodies (500KB+), serializing
1000 entries via JSON.stringify exceeds V8's ~512MB string limit, crashing
the server with RangeError: Invalid string length. Cap stored bodies at
64KB at the journal source (journal.add()), replacing oversized bodies with
a truncation marker { __aimock_truncated: true, originalByteSize: N }.
This bounds aggregate journal memory to ~64MB at maxEntries=1000 and
protects all downstream consumers of journal.getAll(), not just /journal.

Also unifies catch-all error logging to include err.stack unconditionally
for production observability (removes [DIAG] tag from diagnosis branch).

Regression test: 1000 × 100KB entries, JSON.stringify must not throw.
@pkg-pr-new

pkg-pr-new Bot commented Jul 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@copilotkit/aimock@308

commit: 38a5773

…ltibyte regression test

- Change `capBody` gate from `serialized.length` (UTF-16 code units) to
  `Buffer.byteLength(serialized, "utf8")` to match JOURNAL_BODY_CAP_BYTES
  constant name and correctly truncate multibyte content (CJK/emoji) whose
  code-unit count falls under the 64 KB threshold but whose UTF-8 byte size
  does not.

- Rewrite the JOURNAL_BODY_CAP_BYTES JSDoc: the old comment incorrectly
  claimed a ~64 MB aggregate bound enforced by the cap; only the request body
  is capped — headers/path are retained uncapped but bounded by Node's default
  ~16 KB max HTTP header size. The updated doc accurately describes the actual
  mechanism.

- Add regression test: CJK body (22,000 × U+4E2D = ~66 KB UTF-8, ~22 K code
  units) proves the byte-based gate truncates content the old code-unit gate
  missed. Confirmed RED against the old gate, GREEN after this fix.
@jpr5
jpr5 marked this pull request as ready for review July 17, 2026 17:23
@jpr5
jpr5 merged commit 306d1c4 into main Jul 17, 2026
23 checks passed
@jpr5
jpr5 deleted the fix/journal-body-cap branch July 17, 2026 17:24
@jpr5 jpr5 mentioned this pull request Jul 17, 2026
jpr5 added a commit that referenced this pull request Jul 17, 2026
Fixed: cap journal request bodies at 64 KB to prevent ISL crash on /__aimock/journal (#308)
Changed: log full error stack on unhandled request errors for prod diagnosis (#308)
jpr5 added a commit that referenced this pull request Jul 17, 2026
## Release v1.37.2 (patch)

Cuts a patch release for the journal body cap + error stack
instrumentation fix merged in #308.

### Version bump

`package.json` `1.37.1` → `1.37.2`. Also bumps
`packages/aimock-pytest/src/aimock_pytest/_version.py` `AIMOCK_VERSION`
to `1.37.2` (pins the pytest harness to the released npm version).

### CHANGELOG

## [1.37.2] - 2026-07-17

### Fixed

- Prevent `Invalid string length` crash on `GET /__aimock/journal` by
capping retained request bodies at 64 KB (#308)

### Changed

- Log full error stack on unhandled request errors for prod diagnosis
(#308)

### Quality gate

- `pnpm run format:check` — pass
- `pnpm run lint` — pass
- `tsc --noEmit` — pass
- `pnpm test` — 4549 passed (154 files)
- `pnpm run build` — pass
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