claudish 7.50.0 → 7.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/claudish.cjs +80 -4
- package/dist/index.js +1161 -181
- package/package.json +5 -5
package/bin/claudish.cjs
CHANGED
|
@@ -3,8 +3,38 @@
|
|
|
3
3
|
// Launcher script: checks for Bun runtime before starting claudish.
|
|
4
4
|
// Claudish uses Bun-specific APIs (bun:ffi for TUI, Bun.spawn, etc.)
|
|
5
5
|
// so it cannot run under Node.js directly.
|
|
6
|
+
//
|
|
7
|
+
// ── Why this is spawn() and not spawnSync() ──────────────────────────────────
|
|
8
|
+
//
|
|
9
|
+
// This process is a THIN WRAPPER around the real CLI, and it is the process
|
|
10
|
+
// every caller's signal actually lands on. It used to run the child with
|
|
11
|
+
// `spawnSync(bun, ..., { stdio: "inherit" })`, which made the whole tree
|
|
12
|
+
// unkillable in a way that looked like the child was ignoring signals:
|
|
13
|
+
//
|
|
14
|
+
// · `spawnSync` blocks the event loop, so this process cannot forward
|
|
15
|
+
// anything while the child runs.
|
|
16
|
+
// · `stdio: "inherit"` gives the Bun child THIS process's fd 0/1/2. When a
|
|
17
|
+
// caller pipes our stdout, the Bun child holds the write end of that pipe.
|
|
18
|
+
// · So SIGTERM killed only this launcher. The Bun child was ORPHANED, not
|
|
19
|
+
// killed — it kept running, kept billing, and kept the caller's pipe open,
|
|
20
|
+
// writing into it long after the caller had given up.
|
|
21
|
+
//
|
|
22
|
+
// Measured 2026-08-15 with a repro of this exact topology: SIGTERM to the
|
|
23
|
+
// launcher pid leaked 330 B of post-kill output, and so did SIGKILL — because
|
|
24
|
+
// the problem is signalling the wrong process, not a stubborn one. It cost a
|
|
25
|
+
// real `team` run: a model declared TIMEOUT with 0 B wrote a complete 40,699 B
|
|
26
|
+
// answer into the response file 375 s later, and was scored as empty.
|
|
27
|
+
//
|
|
28
|
+
// Now: async spawn, forward the signals a supervisor actually sends, and exit
|
|
29
|
+
// with the child's own status. A caller that terminates `claudish` terminates
|
|
30
|
+
// claudish.
|
|
31
|
+
//
|
|
32
|
+
// This does NOT cover being SIGKILLed ourselves — nothing running in this
|
|
33
|
+
// process can. Callers that need a hard guarantee should spawn claudish
|
|
34
|
+
// `detached` and signal the process GROUP, which reaches the Bun child (and its
|
|
35
|
+
// own descendants) directly; `team-orchestrator.ts` does exactly that.
|
|
6
36
|
|
|
7
|
-
const { execFileSync, execSync } = require("node:child_process");
|
|
37
|
+
const { execFileSync, execSync, spawn } = require("node:child_process");
|
|
8
38
|
const { resolve } = require("node:path");
|
|
9
39
|
|
|
10
40
|
/**
|
|
@@ -75,18 +105,64 @@ Learn more: https://bun.sh`);
|
|
|
75
105
|
process.exit(1);
|
|
76
106
|
}
|
|
77
107
|
|
|
78
|
-
// Exec into bun with the real entry point
|
|
79
108
|
const entry = resolve(__dirname, "..", "dist", "index.js");
|
|
109
|
+
|
|
110
|
+
let child;
|
|
80
111
|
try {
|
|
81
|
-
|
|
112
|
+
child = spawn(bun, [entry, ...process.argv.slice(2)], {
|
|
82
113
|
stdio: "inherit",
|
|
83
114
|
env: process.env,
|
|
84
115
|
});
|
|
85
|
-
process.exit(result.status ?? 1);
|
|
86
116
|
} catch (err) {
|
|
87
117
|
console.error("Failed to start claudish:", err.message);
|
|
88
118
|
process.exit(1);
|
|
89
119
|
}
|
|
120
|
+
|
|
121
|
+
// Forward the signals a supervisor, terminal, or parent process actually sends.
|
|
122
|
+
// SIGKILL is deliberately absent — it cannot be caught, which is the whole
|
|
123
|
+
// reason callers wanting a hard kill must target the process group instead.
|
|
124
|
+
//
|
|
125
|
+
// Handlers are installed only AFTER a successful spawn, so a signal arriving
|
|
126
|
+
// during startup keeps Node's default terminate-immediately behaviour rather
|
|
127
|
+
// than being swallowed by a forward to a child that does not exist yet.
|
|
128
|
+
const FORWARDED =
|
|
129
|
+
process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
130
|
+
for (const signal of FORWARDED) {
|
|
131
|
+
process.on(signal, () => {
|
|
132
|
+
// Best-effort: the child may have exited between the signal and this line.
|
|
133
|
+
try {
|
|
134
|
+
if (!child.killed) child.kill(signal);
|
|
135
|
+
} catch {}
|
|
136
|
+
// Do NOT exit here. Waiting for the child's own "exit" keeps our status
|
|
137
|
+
// honest and, more importantly, keeps this process alive as long as the
|
|
138
|
+
// child is — a caller watching OUR pid must not see us disappear while the
|
|
139
|
+
// real work is still running.
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
child.on("error", (err) => {
|
|
144
|
+
console.error("Failed to start claudish:", err.message);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
child.on("exit", (code, signal) => {
|
|
149
|
+
if (signal) {
|
|
150
|
+
// Re-raise the signal on ourselves so our parent observes the same cause
|
|
151
|
+
// of death the child had, rather than a synthesised exit code. The handler
|
|
152
|
+
// is removed first, or we would forward it straight back to a dead child.
|
|
153
|
+
process.removeAllListeners(signal);
|
|
154
|
+
try {
|
|
155
|
+
process.kill(process.pid, signal);
|
|
156
|
+
return;
|
|
157
|
+
} catch {
|
|
158
|
+
// Re-raise failed (unsupported signal on this platform) — fall back to
|
|
159
|
+
// the shell convention for "died from signal N".
|
|
160
|
+
const NUMBERS = { SIGINT: 2, SIGQUIT: 3, SIGKILL: 9, SIGTERM: 15, SIGHUP: 1 };
|
|
161
|
+
process.exit(128 + (NUMBERS[signal] ?? 0));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
process.exit(code ?? 1);
|
|
165
|
+
});
|
|
90
166
|
}
|
|
91
167
|
|
|
92
168
|
// Running it as a program launches. Requiring it exposes the helpers so the
|