Repository navigation
Large HTTP responses can crash the CLI with Undici Parser.finish assertion #4
Description
Problem
t3code --json request get can terminate with an uncaught Undici assertion when reading a large T3 response. It emits a stack trace instead of the CLI's JSON error envelope. The agent in desktop T3 thread dd878153-f871-437d-8ffd-d097e3902138 fell back to opening state.sqlite read-only to retrieve the requested conversation.
Evidence from desktop-zolder
The thread's recorded tool results on 2026-09-18 show:
- Installed
@bvdm/t3code-cliversion:0.1.2(also verified during this investigation). - T3 server:
0.0.43-nightly.20260918.1895. - Windows Node
26.2.0; its bundled Undici is8.3.0. - The full
/api/orchestration/snapshotrequest failed. Requests to/api/orchestration/threads/5e5fe859-8f7d-4f56-ab85-a2ed3ea65aefand/api/orchestration/snapshot?threadId=5e5fe859-8f7d-4f56-ab85-a2ed3ea65aefrecorded this assertion:
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
assert(!this.paused)
at Parser.finish (node:internal/deps/undici/undici:7247:9)
at Socket.onHttpSocketEnd (node:internal/deps/undici/undici:7679:34)
at Socket.emit (node:events:521:24)
at endReadableNT (node:internal/streams/readable:1744:12)
at process.processTicksAndRejections (node:internal/process/task_queues:90:21)
Windows additionally reported Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 94.
Retrying the thread request through T3's Electron runtime also failed: Node 24.19.0, Undici 7.29.0, the same Parser.finish assertion, exit code 1, and a zero-byte output file. Changing to that Node 24 runtime is therefore not a workaround.
Cause
This matches the upstream HTTP/1 parser bug nodejs/undici#5360. When response backpressure pauses the parser and the peer ends the socket, onHttpSocketEnd invokes Parser.finish, which asserts that the parser is not paused. The exception escapes from the socket event handler rather than rejecting the fetch promise. Upstream merged a fix in nodejs/undici#5474.
The CLI exposes this path by using Node's bundled global fetch and explicitly sending connection: "close" in src/api.ts. Its promise .catch() cannot contain this uncaught event-handler assertion. Process termination also prevents the session-revocation finally from completing; the short session TTL remains the fallback.
The evidence points to a parser/backpressure/socket-close interaction, not a fixed maximum JSON size. Merely increasing a timeout or buffer limit will not fix it. Removing the close header is worth testing, but is not sufficient protection if the server closes the connection itself.
Reproduction
With a running T3 instance containing substantial conversation history:
t3code --json request get /api/orchestration/snapshot | Out-File -Encoding utf8 snapshot.jsonThe following isolated transport reproduction needs no T3 credentials or conversation data. Save it as repro.cjs and run node repro.cjs. It intentionally delays body consumption to exercise backpressure:
const { createServer } = require('node:net');
const body = Buffer.alloc(64 * 1024, 97);
const server = createServer(socket => {
socket.once('data', () => {
socket.write(
'HTTP/1.1 200 OK\r\n' +
`Content-Length: ${body.length}\r\n` +
'Connection: close\r\n\r\n'
);
socket.end(body);
});
});
server.listen(0, '127.0.0.1', async () => {
try {
const response = await fetch(`http://127.0.0.1:${server.address().port}`, {
headers: { connection: 'close' },
});
await new Promise(resolve => setTimeout(resolve, 100));
console.log((await response.text()).length);
} catch (error) {
console.error('Caught request error:', error.message);
} finally {
server.close();
}
});Investigation results:
- Linux Node
26.3.0/ Undici8.3.0: reproduced the identical uncaught assertion, exit 1, bypassing the catch. - Windows Node
26.2.0/ Undici8.3.0: this small synthetic fixture completed in the trial. The original Windows CLI crashes are recorded in the thread. Trigger timing and socket buffering matter. - A normally consumed 8 MiB synthetic JSON response completed with and without
Connection: closeon Linux. Size alone does not reproduce the fault. - The desktop's current
doctorreports the T3 server unavailable, so this investigation used recorded desktop failures and an isolated transport fixture rather than claiming a fresh live T3 reproduction.
Suggested fix and acceptance criteria
- Use a transport with the upstream parser fix, or a suitable
node:http/node:httpsimplementation. Adding an Undici dependency alone will not change globalfetch; the request path must actually use the fixed implementation. - Review the forced
Connection: closebehavior and manage transport shutdown explicitly. - Add a child-process regression test for a response that closes while body consumption is backpressured. Cover complete and truncated bodies so assertions cannot take down the test runner.
- Preserve the JSON envelope, complete piped output, and authenticated session issue/revoke lifecycle. Verify normal failures still revoke in
finally. - Keep state discovery read-only; direct SQLite transcript reads should not be required to work around an HTTP crash.
pnpm check passes on the current repository commit f10a469681f91a65d2b22c713fb2fcd2004aba1a: 21 tests, typechecks, and build. The existing suite does not cover this failure. No source changes were made for this investigation.
Activity
MajesteitBart commentedon Sep 18, 2026
@ClarkvdM please fix
ClarkvdM commentedon Sep 18, 2026
The fix is committed locally as
54403e6onclark/issue-4-http-backpressure. It replaces bundled fetch for authenticated API calls with native HTTP/HTTPS, handles truncated bodies throughT3_REQUEST_FAILED, and removes forced exit so large piped JSON finishes writing.pnpm checkpasses: 33 tests, typechecks, and build. Child-process backpressure tests and built-CLI session issue/revoke tests pass; independent code review found no blocking issues. No live Windows/T3 test was performed.Blocked on delivery:
git push -u origin clark/issue-4-http-backpressurereturned HTTP 403, "Permission to MajesteitBart/t3code-cli.git denied to ClarkvdM." No PR was created, and no settings or permissions were changed.Smallest action needed: grant ClarkvdM write access to this repository, or explicitly approve creating a fork and opening the PR from there. The work is preserved in
/home/bartadmin/development/.clark-github/MajesteitBart/t3code-cli/issue-4.ClarkvdM commentedon Sep 18, 2026
Delivery unblocked: PR #5 is ready at f404fa6. Fresh pnpm check passes typechecks, all 34 tests and build; push and PR CI are green on Node 22.16.0 and 24.10.0. Initial CI exposed the existing SQLite warning in stderr; the tests now account for only that specific diagnostic and retain unexpected-error detection. No live Windows/desktop T3 qualification was performed. Nothing was merged or released.