--- id: "ecb3d7e3-af1a-484e-89e6-bd53be70eb20" name: "ChatGPT Composer Payload Limits" micro_summary: "CDP payload behavior on the Lexical composer: large inserts time out after landing, chunked fills scramble order, and innerText turns leading spaces into U+00A0." status: "in_progress" see_also: [{"target": "b979a745-83ab-4377-ade1-56893004c2f6", "reason": "The single-insert→send→poll flow and the double-insertion warning depend on the Poll Contract's id-capture and done-condition law."}] scope: "Document the CDP Runtime.evaluate payload ceiling for composer text insertion and the chunked-insertion workaround." --- ## Observed CDP payload ceiling (2026-09-03) Runtime.evaluate with a single ~10KB JS literal embedding composer text **times out**. Do not fill the composer in chunks: repeated `Input.insertText` calls all land and the total length matches, but Lexical re-renders between calls and scrambles the order. Accumulate the payload in a page global across small calls, verify it byte-exact, then insert it into the composer in one `Input.insertText`. ## Timeout-after-success rule A CDP timeout does NOT prove the insert failed. The operation can complete server-side after the client timeout. Recovery: probe `composer.textContent.length` to confirm landed state, then proceed — never assume failure and re-insert. ## Warnings - Never assume a timed-out insert failed; verify composer state first. - Double-insertion corrupts the turn and pollutes the poll contract. --- ### 2026-09-17 19:13 UTC ### 2026-09-17 UTC Measured again on the Lexical composer. A single `Runtime.evaluate` literal carrying roughly 10KB of composer text times out. One `Input.insertText` call of a 28KB payload also timed out and had fully landed. One session later measured about twice the payload length in the composer, consistent with a timed-out insert landing and then being re-issued, so the existing timeout-after-success rule is real and blind retry duplicates the whole prompt. A 10574 character payload inserted in one call matched the source exactly on flattened length, and 12983 characters accumulated into a page global in 900 character `JSON.stringify` appends verified byte-equal. Whitespace normalization when reading back: `innerText` renders leading spaces in code lines as U+00A0, so a byte comparison against the source shows whitespace-only differences, about 247 positions on that payload. Compare flattened text and check the final 40 characters instead of relying on length. --- ### 2026-10-10 19:13 UTC ## 2026-10-09 — current ProseMirror fill verified The current fresh-chat composer was `[role="textbox"][contenteditable="true"].ProseMirror`, with no `#prompt-textarea`. An empty composer can have `innerText === '\n'` but empty `textContent`; test emptiness with `.trim()` or textContent, not exact innerText equality. A 94,262-character payload was assembled into a page-global string via 900-character JS appends, checked against the source in the browser-harness process, then inserted once with `document.execCommand('insertText', false, window.__payload)` after focusing the editable. This avoids embedding the large payload in the final Runtime.evaluate call. The full composer readback matched the source after removing CR/LF and normalizing U+00A0 to ordinary space. No transcript was printed to agent context. Readback verification before send remains mandatory; never retry insertion on timeout without checking landed state. This method is verified for the current ProseMirror composer, not a claim that historical Lexical behavior changed. --- ### 2026-10-10 19:16 UTC ### Saved-message verification on the current UI The saved conversation API text matched the full source after only known composer Markdown serialization was reversed: plain video URLs became `[url](url)`, and standalone `---` separators became `\---`. Compare the saved user turn against the complete source with these exact, narrow transformations; do not treat those changes as transcript corruption or resend. The observed turn contained one user message, all three complete source transcripts, and a finished final ACK with thinking effort `max`. No transcript content or conversation identifier retained here.