Summary
In interactive mode the transcript blanks — most visibly the bottom region — while the content is still present (scrolling up reveals it). Nothing repaints it until I submit a new message. /resume does not recover it: the resumed session renders once, then blanks again.
Unlike #4222 / #4069 / #2802, there is no crash. Across 13 ~/.copilot/logs/process-*.log files (since 2026-07-24):
| signature |
occurrences |
Maximum update depth |
0 |
React uncaught |
0 |
write EIO |
0 |
EPIPE |
0 |
The Unregistering foreground session lines in the logs are ordinary session switches with no accompanying error. This is a distinct defect from the React render-loop family.
Root cause
Shipped 1.0.77 bundle, app.js offset ~6070985, function WCr — the measurement hook behind Kc / ScrollBox. Deminified and annotated:
function WCr({ children: t, renderWidth: e, terminalColors: n, colorMode: r, virtualized: o,
viewportHeightRef: s, contentHeightRef: a, contentRef: l,
clearSelectionRef: c, forceUpdate: d, setScrollOffset: u }) {
let [g, p] = useState([]); // g = renderedLines cache
let [m, y] = useState([]); // m = itemPrefixSums cache
let S = useRef(null); // last children
let w = useRef(e); // last renderWidth
// (A) 100ms height poll — invalidates the caches when computed height changes
useEffect(() => {
if (o) return;
let $ = setInterval(() => {
let W = l.current?.yogaNode?.getComputedHeight();
if (W !== undefined && W !== a.current) {
a.current = W;
p([]); // <-- clears renderedLines
y([]); // <-- clears itemPrefixSums
c.current();
u(K => Math.min(K, Math.max(0, W - s.current)));
d();
}
}, 100);
return () => clearInterval($);
}, [o, d, l, a, s, c, u]);
// (B) the effect that REFILLS the caches
useEffect(() => {
if (o) return;
let $ = e !== w.current; // renderWidth changed?
w.current = e;
if (!$ && t === S.current) return; // <-- GUARD: ignores n and r entirely
S.current = t;
let W = setTimeout(() => {
let Y = [], K = [];
React.Children.forEach(t, G => {
K.push(Y.length);
let z = wk(createElement(R, { flexDirection: "column" }, G), e, n ?? undefined, r);
if (z) Y.push(...z.split("\n")); else Y.push("");
});
p(Y); y(K);
}, 0);
return () => clearTimeout(W);
}, [t, e, n, r, o]);
// (C) render-time fallback — an array of EMPTY STRINGS when the cache is empty
let E = React.Children.count(t);
let T = Math.max(E, Math.ceil(a.current));
let x = useMemo(() => Array.from({ length: T }, () => ""), [T]);
let B = g.length > 0 ? g : x; // <-- blank lines
...
}
There are two separate defects here.
1. (A) clears the cache but changes none of (B)'s trigger conditions
p([]) / y([]) are setState calls on g / m, which are not in [t, e, n, r, o]. When the computed height changes while children stay identical — i.e. after a turn finishes and no new timeline entry arrives — (B) never re-runs, g stays [], and (C) renders T empty strings.
Nothing throws. The UI faithfully paints blank lines.
2. The guard in (B) ignores terminalColors and colorMode
n and r are in the dependency array, so a theme change does re-run the effect — but if (!$ && t === S.current) return; bails out before re-measuring, because neither width nor children changed. This is wrong independently of defect 1: the measurement wk(j, e, n, r) genuinely consumes the colors, so a theme change should invalidate the measured lines.
Confirmed empirically: switching themes while blank does not restore the transcript.
Only two escapes exist
renderWidth change or children change. And one layer up:
let { stdout: He } = Ts();
let Ee = He?.columns ?? 80;
let me = P.current || Ee;
// -> WCr({ children: t, renderWidth: me, ... })
renderWidth is derived from columns only, so resizing height alone does not help — the terminal width must change. Ctrl+L also does not recover it, since it changes neither width nor children.
Why each symptom follows
This is the non-virtualized branch (virtualized defaults to false in Kc). The main transcript uses that default; virtualized: true appears only in /tuikit demo screens and one internal list, so there is no user-facing way to avoid the affected path.
Reproduction
Intermittent, tied to height churn during streaming. Frequency drops sharply with --stream off, consistent with (A) being driven by per-delta height changes.
copilot in an interactive terminal
- Submit a prompt that streams a long, height-varying reply (markdown tables / code fences)
- After the turn settles, the transcript blanks
/resume renders once, then blanks again
- Type any new message — content reappears
Workarounds, and why they do or don't work
| workaround |
result |
mechanism |
| resize terminal width |
works |
changes renderWidth (e) → passes the guard |
| type any message |
works |
changes children (t) |
| resize height only |
no effect |
renderWidth tracks columns only |
Ctrl+L |
no effect |
changes neither width nor children |
/theme toggle |
no effect |
defect 2 — guard ignores n / r |
--stream off |
prevents |
removes per-delta height churn, so (A) rarely fires |
Suggested fix
- Make (A) drive the refill — e.g. bump a
measureNonce state in (A) and include it in (B)'s dependency array (or call the measurement routine directly instead of only clearing state).
- Include
terminalColors / colorMode in (B)'s guard, not just its dependency array, so theme changes actually re-measure.
- Defensively, keep the previous
g while re-measuring instead of falling back to empty strings, so a missed re-measure degrades to a stale frame rather than a blank one.
(3) alone would remove the user-visible failure even if the invalidation race persists.
Environment
Related
Summary
In interactive mode the transcript blanks — most visibly the bottom region — while the content is still present (scrolling up reveals it). Nothing repaints it until I submit a new message.
/resumedoes not recover it: the resumed session renders once, then blanks again.Unlike #4222 / #4069 / #2802, there is no crash. Across 13
~/.copilot/logs/process-*.logfiles (since 2026-07-24):Maximum update depthReact uncaughtwrite EIOEPIPEThe
Unregistering foreground sessionlines in the logs are ordinary session switches with no accompanying error. This is a distinct defect from the React render-loop family.Root cause
Shipped
1.0.77bundle,app.jsoffset ~6070985, functionWCr— the measurement hook behindKc/ScrollBox. Deminified and annotated:There are two separate defects here.
1. (A) clears the cache but changes none of (B)'s trigger conditions
p([])/y([])aresetStatecalls ong/m, which are not in[t, e, n, r, o]. When the computed height changes whilechildrenstay identical — i.e. after a turn finishes and no new timeline entry arrives — (B) never re-runs,gstays[], and (C) rendersTempty strings.Nothing throws. The UI faithfully paints blank lines.
2. The guard in (B) ignores
terminalColorsandcolorModenandrare in the dependency array, so a theme change does re-run the effect — butif (!$ && t === S.current) return;bails out before re-measuring, because neither width nor children changed. This is wrong independently of defect 1: the measurementwk(j, e, n, r)genuinely consumes the colors, so a theme change should invalidate the measured lines.Confirmed empirically: switching themes while blank does not restore the transcript.
Only two escapes exist
renderWidthchange orchildrenchange. And one layer up:renderWidthis derived fromcolumnsonly, so resizing height alone does not help — the terminal width must change.Ctrl+Lalso does not recover it, since it changes neither width nor children.Why each symptom follows
T = Math.max(childCount, Math.ceil(contentHeight))— the fallback row count comes from the freshly polled height, so rows past the last real child are empty strings.forceUpdateand re-slices.children(t)./resumedoes not help. The resumed session buildschildrenonce; afterwards they are stable, (A) fires again, and the pane blanks. This rules out the "output buffered, dumped on resume" explanation used in Regression of #2802: main pane freezes / output swallowed - infinite React/Ink render loop (Maximum update depth exceeded) returns on v1.0.72+ (VS Code integrated terminal, native Windows) #4222.This is the non-virtualized branch (
virtualizeddefaults tofalseinKc). The main transcript uses that default;virtualized: trueappears only in/tuikitdemo screens and one internal list, so there is no user-facing way to avoid the affected path.Reproduction
Intermittent, tied to height churn during streaming. Frequency drops sharply with
--stream off, consistent with (A) being driven by per-delta height changes.copilotin an interactive terminal/resumerenders once, then blanks againWorkarounds, and why they do or don't work
renderWidth(e) → passes the guardchildren(t)renderWidthtrackscolumnsonlyCtrl+L/themetogglen/r--stream offSuggested fix
measureNoncestate in (A) and include it in (B)'s dependency array (or call the measurement routine directly instead of only clearing state).terminalColors/colorModein (B)'s guard, not just its dependency array, so theme changes actually re-measure.gwhile re-measuring instead of falling back to empty strings, so a missed re-measure degrades to a stale frame rather than a blank one.(3) alone would remove the user-visible failure even if the invalidation race persists.
Environment
latestis 1.0.74)footer.showQuotaisfalse, so this is not Copilot CLI interactive mode turns blank after submitting a prompt in Windows Terminal, while -p mode works #4159logLevel: all; no render errors capturedRelated
Maximum update depthteardown family) — no crash signature here.aR(resetDate)quotaDatecoercion crash) — quota footer disabled.