Repository navigation
Transcript: a sent prompt of megabytes never freezes the chat (literal, collapsed to its head) - #29840
Conversation
…to its head, never freezing the chat Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-df4c966270872064ca90
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Stale: these images are of Gallery before/after for
States
Pick other states with a red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base 5bdd4c9; does not block Shipped in:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-df4c966270872064ca90
…part beside a big paste keeps its Markdown; the pasted estimate measures a huge part's head Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-df4c966270872064ca90
…nt placement; looser timing bound Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-df4c966270872064ca90
|
Found 2 test failures on Blacksmith runners: Failures
|
|
Review (independent Codex gpt-6.1-sol, high, via the review router; full diff from merge base 07efced to this head): BLOCK at 46b8c1b. [P2] [blocking] webviews/src/agent-session/acpmux/conversation/PromptText.tsx:90 - Truncation creates an infinite layout-effect update loop when the displayed head fits within ten lines but the full prompt does not. For example, |
|
Review (subagent claude-opus-5-5 medium): SHIP at 353db48 Two rounds before this one; follow-ups found there are fixed at this head (inline-placed attachments draw their head; plain per part; pasted estimate measures the head; a huge typed part beside pastes starts collapsed). bun test over conversation/ 636 pass, 0 fail; tsc only error is the pre-existing test/latency/probe.ts one. Follow-ups:
|
|
Review (independent Codex gpt-6.1-sol, high, via the review router; full diff from merge base 07efced to this head): BLOCK at 46b8c1b. [P1] [blocking] webviews/src/agent-session/acpmux/conversation/PromptText.tsx:90 - Truncation can trigger an infinite layout-effect update loop. For example, a huge prompt beginning with a 5,000-character base64 data URI followed by 40,000 text lines renders its preview as just |
…mps it (review of #29840) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-df4c966270872064ca90
There was a problem hiding this comment.
6 issues found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="webviews/src/agent-session/acpmux/conversation/hugePrompt.test.tsx">
<violation number="1" location="webviews/src/agent-session/acpmux/conversation/hugePrompt.test.tsx:31">
P3: The test title says 'one literal text node', but the assertions only check for absent tags and the outer span. Either assert a single text node or rename the test to match what it checks.</violation>
<violation number="2" location="webviews/src/agent-session/acpmux/conversation/hugePrompt.test.tsx:67">
P3: The wall-clock `elapsed < 2_000` assertion can flake on a loaded CI runner, and it includes building the 20 MiB fixture. Assert on the equality of heights, which already proves the path, and drop or start the timer after the fixture is built.</violation>
</file>
<file name="webviews/src/agent-session/acpmux/conversation/PromptText.tsx">
<violation number="1" location="webviews/src/agent-session/acpmux/conversation/PromptText.tsx:236">
P2: `PromptTyped` selects literal mode using only this part’s length. A prompt with more than 200,000 typed characters split across smaller parts still parses each part as Markdown or scans it for links and break points when expanded; compute the threshold from aggregate typed text and pass the result to each part.</violation>
</file>
<file name="webviews/src/agent-session/acpmux/model.ts">
<violation number="1" location="webviews/src/agent-session/acpmux/model.ts:1610">
P2: A huge prompt made of one pasted block bypasses this fast path and is estimated as only the 40px paste chip, but `PromptText` still renders its 24px Show more row. Include that forced button in the estimate for huge pasted prompts to avoid underestimating their height.</violation>
<violation number="2" location="webviews/src/agent-session/acpmux/model.ts:1656">
P2: For pasted prompts, this checks each typed part against 200,000 characters instead of the prompt-wide 4,000-character head budget. A 190,000-character part beside a large paste is fully measured even though only its first 4,000 characters render; measure using the remaining head budget.</violation>
</file>
<file name="webviews/src/agent-session/acpmux/conversation/promptCollapse.ts">
<violation number="1" location="webviews/src/agent-session/acpmux/conversation/promptCollapse.ts:50">
P1: `headParts` retains paste parts regardless of size, so a collapsed huge paste still reaches `PasteChip`, which splits the full text to derive its name and line count. Bound those calculations for huge paste chips so collapsing the prompt does not allocate arrays proportional to the paste’s line count.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| if (left <= 0) break; | ||
| const text = typed(part); | ||
| if (text === undefined) { | ||
| out.push(part); |
There was a problem hiding this comment.
P1: headParts retains paste parts regardless of size, so a collapsed huge paste still reaches PasteChip, which splits the full text to derive its name and line count. Bound those calculations for huge paste chips so collapsing the prompt does not allocate arrays proportional to the paste’s line count.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/conversation/promptCollapse.ts, line 50:
<comment>`headParts` retains paste parts regardless of size, so a collapsed huge paste still reaches `PasteChip`, which splits the full text to derive its name and line count. Bound those calculations for huge paste chips so collapsing the prompt does not allocate arrays proportional to the paste’s line count.</comment>
<file context>
@@ -21,6 +21,49 @@ export function collapsedPromptHeight(height: number, lineHeight: number): numbe
+ if (left <= 0) break;
+ const text = typed(part);
+ if (text === undefined) {
+ out.push(part);
+ continue;
+ }
</file context>
| plain?: boolean; | ||
| }) { | ||
| const shown = show(text); | ||
| if (plain || isHugePrompt(text)) return <span className="cv-user__typed">{shown}</span>; |
There was a problem hiding this comment.
P2: PromptTyped selects literal mode using only this part’s length. A prompt with more than 200,000 typed characters split across smaller parts still parses each part as Markdown or scans it for links and break points when expanded; compute the threshold from aggregate typed text and pass the result to each part.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/conversation/PromptText.tsx, line 236:
<comment>`PromptTyped` selects literal mode using only this part’s length. A prompt with more than 200,000 typed characters split across smaller parts still parses each part as Markdown or scans it for links and break points when expanded; compute the threshold from aggregate typed text and pass the result to each part.</comment>
<file context>
@@ -201,8 +222,18 @@ export function PromptText({
+ plain?: boolean;
+}) {
const shown = show(text);
+ if (plain || isHugePrompt(text)) return <span className="cv-user__typed">{shown}</span>;
return isPromptMarkdown(text) ? (
<Markdown className="cv-user__markdown" thumbnails={false}>
</file context>
| const pasted = row.kind === "user" && Boolean(row.pasted?.length); | ||
| // A huge prompt opens collapsed to its first lines whatever it holds (conversation/PromptText.tsx): | ||
| // its height is those lines and Show more, never a lex or measure of megabytes of text. | ||
| if (row.kind === "user" && !pasted && row.from !== "chief" && isHugePrompt(row.text) && !chiefEnvelope(row.text)) { |
There was a problem hiding this comment.
P2: A huge prompt made of one pasted block bypasses this fast path and is estimated as only the 40px paste chip, but PromptText still renders its 24px Show more row. Include that forced button in the estimate for huge pasted prompts to avoid underestimating their height.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/model.ts, line 1610:
<comment>A huge prompt made of one pasted block bypasses this fast path and is estimated as only the 40px paste chip, but `PromptText` still renders its 24px Show more row. Include that forced button in the estimate for huge pasted prompts to avoid underestimating their height.</comment>
<file context>
@@ -1597,6 +1605,15 @@ function measuredRowHeight(
const pasted = row.kind === "user" && Boolean(row.pasted?.length);
+ // A huge prompt opens collapsed to its first lines whatever it holds (conversation/PromptText.tsx):
+ // its height is those lines and Show more, never a lex or measure of megabytes of text.
+ if (row.kind === "user" && !pasted && row.from !== "chief" && isHugePrompt(row.text) && !chiefEnvelope(row.text)) {
+ const height =
+ messageBelow(beforeFooter) +
</file context>
| // A huge typed part draws collapsed to its head (conversation/PromptText.tsx), so its | ||
| // head is all there is to measure. | ||
| textHeight( | ||
| isHugePrompt(part.text) ? promptHead(part.text, HUGE_PROMPT_HEAD_CHARS) : part.text, |
There was a problem hiding this comment.
P2: For pasted prompts, this checks each typed part against 200,000 characters instead of the prompt-wide 4,000-character head budget. A 190,000-character part beside a large paste is fully measured even though only its first 4,000 characters render; measure using the remaining head budget.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/model.ts, line 1656:
<comment>For pasted prompts, this checks each typed part against 200,000 characters instead of the prompt-wide 4,000-character head budget. A 190,000-character part beside a large paste is fully measured even though only its first 4,000 characters render; measure using the remaining head budget.</comment>
<file context>
@@ -1631,7 +1648,17 @@ function measuredRowHeight(
+ // A huge typed part draws collapsed to its head (conversation/PromptText.tsx), so its
+ // head is all there is to measure.
+ textHeight(
+ isHugePrompt(part.text) ? promptHead(part.text, HUGE_PROMPT_HEAD_CHARS) : part.text,
+ contentWidth,
+ entry.prepared,
</file context>
| // Markdown-shaped, so a small one would go through the Markdown renderer. | ||
| const markdown = (chars: number) => `# Log\n- item **bold** \`code\`\n${"x".repeat(chars)}`; | ||
|
|
||
| test("a huge typed part draws as one literal text node, with no break points or Markdown", () => { |
There was a problem hiding this comment.
P3: The test title says 'one literal text node', but the assertions only check for absent tags and the outer span. Either assert a single text node or rename the test to match what it checks.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/conversation/hugePrompt.test.tsx, line 31:
<comment>The test title says 'one literal text node', but the assertions only check for absent tags and the outer span. Either assert a single text node or rename the test to match what it checks.</comment>
<file context>
@@ -0,0 +1,162 @@
+// Markdown-shaped, so a small one would go through the Markdown renderer.
+const markdown = (chars: number) => `# Log\n- item **bold** \`code\`\n${"x".repeat(chars)}`;
+
+test("a huge typed part draws as one literal text node, with no break points or Markdown", () => {
+ const text = markdown(HUGE_PROMPT_CHARS);
+ const html = renderToStaticMarkup(<PromptTyped text={text} show={same} />);
</file context>
| test("a huge prompt's row is estimated as its collapsed lines, quickly", () => { | ||
| const { lineHeight } = currentChatMetrics(); | ||
| const row = (id: string, text: string) => ({ id, version: 1, at: 0, kind: "user", text }); | ||
| const started = performance.now(); |
There was a problem hiding this comment.
P3: The wall-clock elapsed < 2_000 assertion can flake on a loaded CI runner, and it includes building the 20 MiB fixture. Assert on the equality of heights, which already proves the path, and drop or start the timer after the fixture is built.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/conversation/hugePrompt.test.tsx, line 67:
<comment>The wall-clock `elapsed < 2_000` assertion can flake on a loaded CI runner, and it includes building the 20 MiB fixture. Assert on the equality of heights, which already proves the path, and drop or start the timer after the fixture is built.</comment>
<file context>
@@ -0,0 +1,162 @@
+test("a huge prompt's row is estimated as its collapsed lines, quickly", () => {
+ const { lineHeight } = currentChatMetrics();
+ const row = (id: string, text: string) => ({ id, version: 1, at: 0, kind: "user", text });
+ const started = performance.now();
+ const [huge] = [...layoutConversation([row("h", markdown(20 * 1024 * 1024))] as never, 720).heights];
+ const elapsed = performance.now() - started;
</file context>
… with no dead Show more (review of #29840) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-df4c966270872064ca90
|
Review (codex gpt-6.1-sol xhigh, router-review): BLOCK at 82d3f2e BLOCK at 82d3f2e Visual verdict: no images; Leo tries the build
|
There was a problem hiding this comment.
1 existing issue remains and 1 new issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="webviews/src/agent-session/acpmux/model.ts">
<violation number="1" location="webviews/src/agent-session/acpmux/model.ts:1617">
P2: A large inline text-file part fails this guard because `hasHugeTypedPart` ignores `kind: "file"`, so the estimator processes its full `row.text` even though the body renders as a chip. Estimate visible inline parts without lexing chip bodies.</violation>
</file>
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
View guided diff | Turn on auto-fix | Re-trigger cubic
| !pasted && | ||
| row.from !== "chief" && | ||
| isHugePrompt(row.text) && | ||
| (!row.inlineParts || hasHugeTypedPart(row.inlineParts)) && |
There was a problem hiding this comment.
P2: A large inline text-file part fails this guard because hasHugeTypedPart ignores kind: "file", so the estimator processes its full row.text even though the body renders as a chip. Estimate visible inline parts without lexing chip bodies.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/model.ts, line 1617:
<comment>A large inline text-file part fails this guard because `hasHugeTypedPart` ignores `kind: "file"`, so the estimator processes its full `row.text` even though the body renders as a chip. Estimate visible inline parts without lexing chip bodies.</comment>
<file context>
@@ -1607,7 +1608,15 @@ function measuredRowHeight(
+ !pasted &&
+ row.from !== "chief" &&
+ isHugePrompt(row.text) &&
+ (!row.inlineParts || hasHugeTypedPart(row.inlineParts)) &&
+ !chiefEnvelope(row.text)
+ ) {
</file context>
|
Review (codex, router-review): SHIP at 415434f Reviewer: codex gpt-6.1-sol xhigh, fast tier. Follow-ups: 2. Each lands as an issue in this repo that links this receipt; the lane that opened the PR fixes it forward. SHIP at 415434f Visual verdict: no images; Leo tries the build
Follow-ups:
|
A newer verdict blocks 5b14814 (no independent SHIP at the head).
|
Head moved 415434f -> 5b14814 by cmux-bot's base merge only (parents 415434f and feat-cmux-next 5bdd4c9). The PR's own diff is identical: |
|
Reds at 5b14814, compared against base before landing:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Leo, 2026-10-31: "surface_id=tab_c487e18b4f0083b34190540cd40eaa7a this is completely hardlocked bc of message size"
The transport side of this ask has already landed: #29670 (acpmux message limit 15 MiB → 31 MiB), #28754 (a chat whose records add up past one message still opens), #28778 (an oversized prompt or chat is never retried unchanged), #29500 (a prompt's attachments never add up past one message). This PR covers the render side: a chat that holds a prompt of megabytes no longer freezes the pane once it opens.
Before
A sent prompt draws through
PromptText. A long prompt is collapsed with CSS only, so the whole text is still rendered: it goes through Markdown when it looks like Markdown, gets a URL scan, andsoftBreaksadds a<wbr>after every/ - _ . : = & ? # @ ,in each long token. For a multi-MB paste or a base64 blob that means hundreds of thousands of DOM nodes, laid out on the main thread for text the clamp hides. The row estimate (model.ts) also lexed orprepared the whole text.After
HUGE_PROMPT_CHARS(200k characters) draws as one literal text node: no Markdown, no link scan, no break points.HUGE_PROMPT_HEAD_CHARS(4,000 characters, which is well over ten lines at any width). Show more renders the whole text, still as one text node. Copy, Edit, Retry and Find still use the whole prompt (Find opens it on a match, as before).Tests
conversation/hugePrompt.test.tsx: checks that a huge Markdown-shaped part renders literally with no<wbr>, a 5 MiB blob renders only its head while collapsed, the head never splits a surrogate pair, and a 20 MiB prompt's row estimate matches a long prompt's collapsed height in under 500 ms.Risk
Low. The change is limited to the sent-prompt bubble and its row estimate, and it only applies to prompts over 200k characters. A huge prompt that is Markdown now shows as plain text, which is intended.
Agent session: agent session
🤖 Generated with Claude Code