Preserve WebCRT connect errors instead of masking them as websocket 4502.

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-11 16:59:44 +08:00
parent b6a579ec02
commit 35e7bb4667

View file

@ -448,6 +448,8 @@ export const WebTerminal = forwardRef<WebTerminalHandle, Props>(function WebTerm
// report closed/error after a newer socket owns the terminal. // report closed/error after a newer socket owns the terminal.
let cancelled = false; let cancelled = false;
let ws: WebSocket | null = null; let ws: WebSocket | null = null;
// Server sends status:error then close(4502); ignore the close code so UI keeps the real reason.
let terminalFailureReported = false;
onStatusRef.current?.("connecting"); onStatusRef.current?.("connecting");
const isActiveSocket = () => !cancelled && !!ws && wsRef.current === ws; const isActiveSocket = () => !cancelled && !!ws && wsRef.current === ws;
@ -535,12 +537,16 @@ export const WebTerminal = forwardRef<WebTerminalHandle, Props>(function WebTerm
cliHop: typeof msg.cli_hop === "boolean" ? msg.cli_hop : undefined, cliHop: typeof msg.cli_hop === "boolean" ? msg.cli_hop : undefined,
} }
: undefined; : undefined;
onStatusRef.current?.(String(msg.state || ""), msg.message, phase, meta); const state = String(msg.state || "");
if (msg.state === "connected" || msg.state === "connecting") { if (state === "error" || state === "closed") {
terminalFailureReported = true;
}
onStatusRef.current?.(state, msg.message, phase, meta);
if (state === "connected" || state === "connecting") {
maybeFocus(); maybeFocus();
return; return;
} }
if (msg.state === "warning") { if (state === "warning") {
const m = String(msg.message || ""); const m = String(msg.message || "");
const dropMatch = /^queue_dropped:(\d+)/i.exec(m); const dropMatch = /^queue_dropped:(\d+)/i.exec(m);
if (dropMatch) { if (dropMatch) {
@ -550,11 +556,11 @@ export const WebTerminal = forwardRef<WebTerminalHandle, Props>(function WebTerm
} }
return; return;
} }
if (msg.state === "closed" || msg.state === "error") { if (state === "closed" || state === "error") {
const detail = msg.message ? `: ${msg.message}` : ""; const detail = msg.message ? `: ${msg.message}` : "";
term.writeln( term.writeln(
`\r\n\x1b[33m${tRef.current("webcrt.term.sessionStatus", { `\r\n\x1b[33m${tRef.current("webcrt.term.sessionStatus", {
state: String(msg.state || ""), state,
detail, detail,
})}\x1b[0m`, })}\x1b[0m`,
); );
@ -569,12 +575,17 @@ export const WebTerminal = forwardRef<WebTerminalHandle, Props>(function WebTerm
socket.onerror = () => { socket.onerror = () => {
if (!isActiveSocket()) return; if (!isActiveSocket()) return;
terminalFailureReported = true;
onStatusRef.current?.("error", "websocket_error"); onStatusRef.current?.("error", "websocket_error");
}; };
socket.onclose = (ev) => { socket.onclose = (ev) => {
// Intentional unmount/remount closes the socket; do not flip UI to "closed". // Intentional unmount/remount closes the socket; do not flip UI to "closed".
if (!isActiveSocket()) return; if (!isActiveSocket()) return;
// 45xx = app close after status:error (e.g. connect_timeout → 4502).
if (terminalFailureReported && ev.code >= 4000 && ev.code < 5000) {
return;
}
onStatusRef.current?.("closed", `websocket_closed:${ev.code}`); onStatusRef.current?.("closed", `websocket_closed:${ev.code}`);
}; };