fix: v2.5.8 — the browser terminal attaches on Windows

node-pty on Windows looks the file it starts up on PATH exactly as given and
never adds `.exe`, so `tmux attach-session` started as a bare `tmux` failed
with "File not found" on every attach: the browser's terminal socket opened,
closed, and reconnected every 1.5 s.

- pty-attach.js: attachBinary names tmux by its full file name on Windows
  (ai-tools.which("tmux"), else tmux.exe)
- websocket.js: an attach that throws is logged as `[WS] attach <run> failed`,
  the one place that says why the terminal keeps reconnecting
This commit is contained in:
2026-10-07 00:23:32 +07:00
parent 4163f431bb
commit 7543b8d62a
8 changed files with 31 additions and 9 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -7233,7 +7233,7 @@ exports[`screen snapshots > Settings — about 1`] = `
<p <p
class="text-[10px] text-status-warning/90 mt-1" class="text-[10px] text-status-warning/90 mt-1"
> >
UI build v2.5.7 (rebuild client to match) UI build v2.5.8 (rebuild client to match)
</p> </p>
</div> </div>
<div <div
+1 -1
View File
@@ -3,7 +3,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: Agent Dashboard for Claude Code API title: Agent Dashboard for Claude Code API
version: 2.5.7 version: 2.5.8
description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence. description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence.
contact: contact:
name: Nguyễn Ngọc Trí Vĩ name: Nguyễn Ngọc Trí Vĩ
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "agenthub", "name": "agenthub",
"version": "2.5.7", "version": "2.5.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "agenthub", "name": "agenthub",
"version": "2.5.7", "version": "2.5.8",
"hasInstallScript": true, "hasInstallScript": true,
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "agenthub", "name": "agenthub",
"version": "2.5.7", "version": "2.5.8",
"bin": { "bin": {
"agenthub": "bin/agenthub.js" "agenthub": "bin/agenthub.js"
}, },
+14
View File
@@ -169,3 +169,17 @@ describe("pty-attach", () => {
assert.equal(killed, true); assert.equal(killed, true);
}); });
}); });
describe("attachBinary on Windows", () => {
it("names tmux with its extension — node-pty there never adds `.exe`", () => {
// A bare `tmux` made node-pty answer "File not found" on every attach, so
// the browser's terminal opened, closed and reconnected every 1.5 s.
const saved = process.env.PATH;
process.env.PATH = "";
try {
assert.equal(ptyAttach.attachBinary("win32"), "tmux.exe");
} finally {
process.env.PATH = saved;
}
});
});
+8 -3
View File
@@ -42,9 +42,14 @@ function __setSpawnImpl(fn) {
* file), else `tmux` on PATH. A client must match its server — tmux installed * file), else `tmux` on PATH. A client must match its server — tmux installed
* in a newer version while the server runs makes PATH's client fail every * in a newer version while the server runs makes PATH's client fail every
* attach with "open terminal failed: not a terminal", though every other tmux * attach with "open terminal failed: not a terminal", though every other tmux
* command still works. Linux only; elsewhere PATH's tmux as before. * command still works. Linux only; elsewhere PATH's tmux as before — on
* Windows by its full file name: node-pty there looks the file up on PATH as
* given, never adding `.exe`, so a bare `tmux` failed every attach with
* "File not found" (the browser saw a socket that opened and closed, again
* every 1.5 s).
*/ */
function attachBinary() { function attachBinary(platform = process.platform) {
if (platform === "win32") return require("./ai-tools").which("tmux") || "tmux.exe";
const pid = tmux.serverPid(); const pid = tmux.serverPid();
if (!pid) return "tmux"; if (!pid) return "tmux";
const exe = `/proc/${pid}/exe`; const exe = `/proc/${pid}/exe`;
@@ -153,4 +158,4 @@ function attach(ws, runId, { cols, rows }) {
// resolves a working native binding with no compiler needed. // resolves a working native binding with no compiler needed.
__setSpawnImpl((...args) => require("@lydell/node-pty").spawn(...args)); __setSpawnImpl((...args) => require("@lydell/node-pty").spawn(...args));
module.exports = { attach, validateRunId, __setSpawnImpl }; module.exports = { attach, attachBinary, validateRunId, __setSpawnImpl };
+3
View File
@@ -121,6 +121,9 @@ function initPtyWebSocket(server) {
try { try {
attach(ws, runId, size || { cols: 80, rows: 24 }); attach(ws, runId, size || { cols: 80, rows: 24 });
} catch (err) { } catch (err) {
// The browser only sees a socket that closed and retries every 1.5 s;
// this line is the one place that says why.
console.warn(`[WS] attach ${runId} failed:`, err.message);
try { try {
ws.close(1008, err.message); ws.close(1008, err.message);
} catch { } catch {