[Bug]: Terminal clear never synchronizes the ConPTY screen buffer #36007
Send to my agentSummary#
Clearing a terminal resets client output and server history without clearing the Windows PTY's internal screen representation. This leaves ConPTY out of sync with the cleared frontend, a condition node-pty documents as allowing the old screen to be reprinted. A focused terminal-manager probe confirmed the missing PTY clear call while its history-clear and lifecycle-event controls passed; native Windows screen reprinting was not tested.
Before submitting#
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area#
apps/server
Steps to reproduce#
- Use a checkout of the commit below with its pinned dependencies installed.
- Save the script below as repro.sh and run sh repro.sh /path/to/checkout.
- The script extends the existing manager fixture in a temporary test, produces retained output, waits for its delivery, calls terminal.clear, and checks both successful history clearing and the PTY clear call.
The missing call reproduced once in one attempt. No live Windows shell or frontend was run.
The script requires Node.js and the checkout's installed Vite+ executable and pinned dependencies. It creates disposable HOME, TMPDIR, and T3CODE_HOME directories, removes its temporary test and state on exit, and never spawns or signals a real shell. Exit 0 means the expected backend synchronization, 1 means missing synchronization after the control passed, and 2 means setup or the control was inconclusive.
Reproduction script: repro.sh
#!/bin/sh
set -u
if [ "$#" -ne 1 ]; then echo 'RESULT: inconclusive'; exit 2; fi
checkout=$(cd "$1" && pwd) || { echo 'RESULT: inconclusive'; exit 2; }
scratch=$(mktemp -d)
test_file="$checkout/apps/server/src/terminal/Manager.pty-lifecycle.test.ts"
if [ -e "$test_file" ]; then rm -rf "$scratch"; echo 'RESULT: inconclusive'; exit 2; fi
cleanup() { rm -f "$test_file"; rm -rf "$scratch"; }
trap cleanup EXIT HUP INT TERM
export HOME="$scratch/home" TMPDIR="$scratch/tmp" T3CODE_HOME="$scratch/t3"
mkdir -p "$HOME" "$TMPDIR" "$T3CODE_HOME"
printf 'node: '; node --version
printf 'vp: '; "$checkout/node_modules/.bin/vp" --version
(cd "$checkout/apps/server" && node -p '"node-pty: " + require("node-pty/package.json").version')
sed '$d' "$checkout/apps/server/src/terminal/Manager.test.ts" > "$test_file"
cat >> "$test_file" <<'TEST'
it.effect("pty-lifecycle clear synchronizes the Windows PTY screen", () =>
Effect.gen(function* () {
const { manager, ptyAdapter, getEvents } = yield* createManager();
const input = openInput();
yield* manager.open(input);
const process = ptyAdapter.processes[0]!;
const clear = vi.fn();
Object.assign(process, { clear });
const output = yield* Deferred.make<void>();
const unsubscribe = yield* manager.subscribe((event) =>
event.type === "output" ? Deferred.succeed(output, undefined).pipe(Effect.asVoid) : Effect.void,
);
process.emitData("retained screen\n");
yield* Deferred.await(output);
unsubscribe();
yield* manager.clear(input);
const cleared = (yield* getEvents).some(event => event.type === "cleared");
assert.isTrue(cleared);
assert.equal((yield* manager.open(input)).history, "");
console.log("CONTROL: clear published its event and emptied retained history");
console.log(`OBSERVED: PTY clear calls=${clear.mock.calls.length}`);
if (clear.mock.calls.length === 0) console.log("DEFECT: frontend clear does not synchronize the PTY screen");
assert.equal(clear.mock.calls.length, 1);
}).pipe(Effect.provideService(HostProcess.Platform, "win32")),
);
});
TEST
(cd "$checkout" && node_modules/.bin/vp test run "$test_file" -t 'pty-lifecycle clear') > "$scratch/output" 2>&1
status=$?
cat "$scratch/output"
if ! grep -q 'CONTROL: clear published its event and emptied retained history' "$scratch/output"; then echo 'RESULT: inconclusive'; exit 2; fi
if [ "$status" -eq 0 ]; then echo 'RESULT: expected behavior'; exit 0; fi
if grep -q 'DEFECT: frontend clear does not synchronize the PTY screen' "$scratch/output"; then echo 'RESULT: defect reproduced'; exit 1; fi
echo 'RESULT: inconclusive'; exit 2
Expected behavior#
Clearing client output should keep the Windows PTY's screen representation synchronized with the frontend. The pinned node-pty IPty.clear contract#L168-L174 describes clearing the internal buffer specifically when the frontend is cleared, to avoid ConPTY reprinting its screen, and defines it as a no-op on other platforms. T3's packages/client-runtime/src/state/terminalSession.ts#L193-L199 resets the client's retained output to empty on cleared, and apps/web/src/terminal/ghostty/core.test.ts#L366-L400 establishes that clear resets the real frontend emulator.
Actual behavior#
After terminal.clear succeeds, the manager emits cleared and empties retained history but makes zero calls to the process's clear method. The focused probe supplies a clear recorder on the existing FakePtyProcess and injects platform win32. Source inspection also establishes that PtyProcess and NodePtyProcess expose no clear operation at all, although the pinned native IPty exposes clear specifically for synchronizing a frontend clear with ConPTY. This confirms missing synchronization, not native Windows screen redraw behavior.
Impact#
Minor bug or occasional failure
Impact: P2? - The Windows clear API leaves the backend and cleared frontend screen representations inconsistent. The pinned dependency documents possible screen reprinting as the consequence, but the native Windows visual result and its frequency remain unmeasured.
The omission is deterministic for terminal.clear on a running Windows PTY and affects the shared server API. NOT_ENOUGH_INFO: which subsequent native Windows operation causes old content to reappear, how often it happens, and a verified workaround; observing a clear followed by resize or output in a Windows client would establish those facts. macOS and Linux need no backend buffer synchronization under the pinned clear contract.
Version or commit#
T3 Code commit 59e64eef881632a23f89c4138bcf37f23d92bf93; node-pty 1.2.0-beta.15 with the repository's patch.
Environment#
Probe host: macOS arm64, Node.js v26.12.1, Vite+ 1.4.0, Vitest 5.0.5, Effect 4.1.3. Platform is injected as win32 with the existing fake PTY manager fixture. Native Windows screen rendering was not run.
Evidence#
The focused script printed:
CONTROL: clear published its event and emptied retained history
OBSERVED: PTY clear calls=0
DEFECT: frontend clear does not synchronize the PTY screen
AssertionError: expected +0 to equal 1
RESULT: defect reproduced
This is an excerpt; machine-local runner paths and stack traces are omitted. apps/server/src/terminal/Manager.ts#L3494-L3514 clears history and sends cleared without touching the PTY. The exposed packages/shared/src/PtyAdapter.ts#L26-L33 has no clear method, and apps/server/src/terminal/NodePtyAdapter.ts#L170-L232 forwards other PTY operations without exposing clear. Upstream windowsTerminal.ts#L160-L164 implements native clear, while windowsPtyAgent.ts#L192-L194 forwards it to the native ConPTY backend.
Restoration check#
A successful terminal.clear should clear retained history, notify attached clients, and synchronize the running ConPTY screen buffer. A native Windows follow-up should verify that subsequent redraws do not resurrect pre-clear screen content.
Interface#
API: WebSocket terminal.clear and TerminalManager.clear.
Earliest failure stage#
State or storage
References#
- https://github.com/pingdotgg/t3code/pull/25192 — preserves queued exit events while clearing scrollback; this report concerns the separate missing synchronization with ConPTY's internal screen and does not reproduce that queued-exit bug.
tanstack-source added labelsbug
juliusmarminge closed this
Sign in to join this conversation.
I tried to reproduce this on the current build and could not.
On origin/main 939b2884f9: read TerminalManager.clear (apps/server/src/terminal/Manager.ts:3494), which clears history and publishes 'cleared' without touching the PTY. packages/shared/src/PtyAdapter.ts PtyProcess has no clear method and NodePtyProcess does not forward IPty.clear(). The installed node-pty 1.2.0-beta.15 typings document IPty.clear() as 'useful if the buffer is cleared on the frontend in order to synchronize state with the backend to avoid ConPTY possibly reprinting the screen', and its windowsTerminal implements it. Added a clear() call counter to FakePtyProcess in apps/server/src/terminal/Manager.test.ts and a test that opens a terminal, calls manager.clear and expects one PTY clear call;
vp test run apps/server/src/terminal/Manager.test.ts -t "screen buffer"fails with 0 calls. Not seen in a running app: the visible symptom (ConPTY reprinting old content) needs a native Windows host, which no worker has, and the reporter did not observe it either.Closing as not planned. If it still happens for you, reply with the exact steps and the version you are on and it will be looked at again.