[Bug]: Codex fork requests full history, exceeding transport limits on large threads#24460
Before submitting
- I searched existing issues and pull requests and did not find a report specifically covering the remaining full-history Codex fork request.
- I included observed behavior, measurements, source references, and verification limits.
Area
apps/server — Codex adapter and app-server transport
Summary
Forking a large Codex conversation requests the complete historical thread.turns payload even though the adapter returns thread metadata. On the installed build this repeatedly left every new Codex and Claude turn stuck on Thinking. Current main has since added a 128 MiB transport limit, but forkThread still omits excludeTurns: true, so the oversized response remains an obstacle to forking these conversations.
This is a follow-up to the already-fixed unbounded-input issue, not a claim that current main still exhibits the same global hang.
Steps to reproduce
Observed on the installed desktop build:
- Use an existing Codex conversation with extensive tool/file-change history. In this case, reading the resulting fork's full history produced one approximately 590 MB JSON-RPC response.
- Fork from a completed assistant response and send the first message in the new thread.
- T3 sends
thread/forkwiththreadIdandlastTurnId, withoutexcludeTurns. - Continue three other Codex threads sharing that app-server process.
- Send a short message in a new thread, including a Claude thread.
The fork and three resume operations remained running, occupying all four effect workers; subsequent starts and title-generation effects remained pending. Restarting the T3 backend restored service. Retrying the same fork reproduced the stall after restart.
The live reproduction used existing private history, not a shareable clean-session fixture. No private transcript or database is attached.
Expected behavior
Forking should preserve the provider conversation without returning its complete historical turn payload when the adapter only needs metadata. An oversized response must not strand pending requests or block unrelated providers.
Actual behavior and evidence
Sanitized request shape captured on both attempts:
| 1 | { |
| 2 | "method": "thread/fork", |
| 3 | "params": { |
| 4 | "threadId": "<source-thread>", |
| 5 | "lastTurnId": "<completed-turn>", |
| 6 | "cwd": "<workspace>", |
| 7 | "model": "gpt-6-astra" |
| 8 | } |
| 9 | } |
The shared Codex process remained alive and logged outgoing events after T3 stopped recording incoming protocol messages. Terminating that Codex process did not release the waiting operations; restarting the T3 backend did. After the first restart, separate Codex and Claude ping tests both completed, before retrying the fork reproduced the stall.
An isolated app-server read-only probe of the already-created fork, using thread/read with includeTurns: true, measured:
| 1 | Response bytes: 590,282,432 |
| 2 | Response UTF-16 code units: 589,429,682 |
| 3 | Read completed in: 2.68 seconds |
| 4 | T3 runtime MAX_STRING_LENGTH: 536,870,888 |
These are measurements of a separate thread/read response for the fork, not a captured original thread/fork response. No inference turn was started by the probe.
The installed Electron runtime also reproduced the line-assembly failure with synthetic fragments:
| 1 | const { MAX_STRING_LENGTH } = require("node:buffer").constants; |
| 2 | const size = 589429682; |
| 3 | const fragment = "x".repeat(65536); |
| 4 | const fragments = Array(Math.floor(size / fragment.length)).fill(fragment); |
| 5 | fragments.push("x".repeat(size % fragment.length)); |
| 6 | console.log(MAX_STRING_LENGTH); // 536870888 |
| 7 | fragments.join(""); // RangeError: Invalid string length |
No original live RangeError stack was captured. The reader-fiber failure explanation is supported by the payload measurement, synthetic failure, source inspection, and repeated live symptoms; it is not an instrumented trace of the original exception.
Source inspection and current-main status
Installed build: 81493efb71e8e8244f95d3e9f71d8369654ab151 (2026-10-15).
Checked main: d53873b6a4799e87afd63c8d288ef0089f5a3748 (2026-10-24).
- Current
forkThreadstill callsclient.request("thread/fork", ...)withoutexcludeTurns. The ordinary resume path already sets it. - Installed protocol reader joins unbounded fragments inside an Effect fiber. The typed failure handler does not cover a thrown defect at that join, and requests await their deferred responses without a deadline.
- Current protocol reader now rejects messages above 128 MiB before joining, via merged #19733 (2026-10-20). This mitigates the unbounded join, but the measured history response also exceeds this new limit.
- Effect worker concurrency defaults to four, matching the four running starts observed in the live outbox.
Current main was inspected, not executed against this history. Successful fork behavior or the exact user-visible error/recovery behavior after #19733 has not been verified here.
Suggested direction
Request excludeTurns: true on the fork path, ensuring it survives generated-schema encoding (the resume path uses a raw request for this reason). Verify the fork still preserves context and boundary semantics while only returning metadata. Add focused coverage for the outgoing fork request and for pending-request completion when the transport reader fails.
Codex documents the metadata-only fork option in ThreadForkParams.
Impact
Blocks work completely on the installed build once the shared workers are occupied. The remaining current-main concern is that large native forks still request responses exceeding the transport limit.
Version and environment
- T3 Code Beta desktop, package/build version
0.1.0, commit81493efb71e8. - macOS 26.6.2 (25G83), Apple Silicon.
- Bundled Electron 44.4.5 / Node.js v24.21.0.
- Codex CLI 0.164.0, shared local app-server, GPT-6-Astra.
Related issues and fixes
- #7075 (closed; #6399 duplicate): full-history resume responses. This report concerns the still-unbounded fork request.
- #12884, fixed by #19733: missing pre-parse message-size bound. That mitigation is present on current main and absent from the affected installation.
- #16502 / #17045 and open #18764: shared app-server exit/unresponsiveness recovery. In this incident the initial stall occurred while Codex was still alive; killing it afterward did not recover the session.
Workaround
Restart the T3 backend to release the stuck work, then avoid retrying the affected fork. A fresh conversation avoids copying that provider history. An update containing #19733 should provide the size guard, but has not been tested here and does not by itself remove full-history fork requests. No application source or live database was manually modified.