You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'd like to work on this. I've reproduced the two failure modes in the code paths for the external HTTP entry and the streaming tool-call parser, and I'm preparing a focused fix with regression tests for arbitrary chunk boundaries.
Plan:
trace HTTP request body -> chat message -> provider request, and add repeatable tests for each stage;
fix the truncation at its actual source (no fallback paths);
cover tool tag boundaries and arbitrary chunk splits with unit tests;
verify with a build plus an executable end-to-end check.
Thank you so much for looking into this! Your plan looks perfect. I'm using a non-root Android device (ColorOS) and your fix will completely solve my background automation blockers. Looking forward to the PR!
The two symptoms share one root cause. TextStreamEvent only carried (eventType, id), so each consumer derived the rollback offset from its own buffer. Because content chunks and revision events are collected by separate coroutines from separate replayable channels, the consumer's offset had usually already advanced past the aborted attempt's prefix by the time it dequeued SAVEPOINT. ROLLBACK then truncated to that stale offset and left the fragment behind. The HTTP entry's extra hops (NanoHTTPD worker → runBlocking → serviceScope → shareRevisable) widen that scheduling gap, which is why the API path reproduced far more often than manual input.
I also probed the entry side directly: POST /api/external-chat with bodies well beyond NanoHTTPD's 8192-byte buffer arrives byte-exact (verified by length and SHA-256), so the text that reached the model was an aborted-attempt prefix rather than a truncated body.
The fix makes the producer record the authoritative offset and the discarded range on every event, keeps the raw chunk sequence as the coordinate space, and rebuilds the final text from chunk plus event replay in every consumer. 1365 unit tests pass, including new coverage for arbitrary chunk splits, tool-tag boundaries, and consecutive aborted retries. End-to-end on an emulator: a 981-character HTTP message keeps the same SHA-256 through every stage, the first attempt emitting <tool_L6Gi is rolled back to length 0, and the retry's complete <tool_AB12 name="device_info"> call is parsed and executed.
Thank you so much! I saw my exact log fragment <tool_L6Gi in your E2E test description. This is amazing. Your fix completely explains the scheduling gap issue. Can't wait to see it merged!
Activity
yoruuuchan commentedon Oct 23, 2026
I'd like to work on this. I've reproduced the two failure modes in the code paths for the external HTTP entry and the streaming tool-call parser, and I'm preparing a focused fix with regression tests for arbitrary chunk boundaries.
Plan:
Will link the PR here once it's ready.
badaixue commentedon Oct 23, 2026
Thank you so much for looking into this! Your plan looks perfect. I'm using a non-root Android device (ColorOS) and your fix will completely solve my background automation blockers. Looking forward to the PR!
yoruuuchan commentedon Oct 24, 2026
Opened PR #1414 against
dev: #1414The two symptoms share one root cause.
TextStreamEventonly carried(eventType, id), so each consumer derived the rollback offset from its own buffer. Because content chunks and revision events are collected by separate coroutines from separate replayable channels, the consumer's offset had usually already advanced past the aborted attempt's prefix by the time it dequeuedSAVEPOINT.ROLLBACKthen truncated to that stale offset and left the fragment behind. The HTTP entry's extra hops (NanoHTTPD worker →runBlocking→serviceScope→shareRevisable) widen that scheduling gap, which is why the API path reproduced far more often than manual input.I also probed the entry side directly:
POST /api/external-chatwith bodies well beyond NanoHTTPD's 8192-byte buffer arrives byte-exact (verified by length and SHA-256), so the text that reached the model was an aborted-attempt prefix rather than a truncated body.The fix makes the producer record the authoritative offset and the discarded range on every event, keeps the raw chunk sequence as the coordinate space, and rebuilds the final text from chunk plus event replay in every consumer. 1365 unit tests pass, including new coverage for arbitrary chunk splits, tool-tag boundaries, and consecutive aborted retries. End-to-end on an emulator: a 981-character HTTP message keeps the same SHA-256 through every stage, the first attempt emitting
<tool_L6Giis rolled back to length 0, and the retry's complete<tool_AB12 name="device_info">call is parsed and executed.badaixue commentedon Oct 24, 2026
Thank you so much! I saw my exact log fragment <tool_L6Gi in your E2E test description. This is amazing. Your fix completely explains the scheduling gap issue. Can't wait to see it merged!