claude-use 2.5.2 → 2.5.4

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +161 -51
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -221824,7 +221824,7 @@ var program = new Command();
221824
221824
  // package.json
221825
221825
  var package_default = {
221826
221826
  name: "claude-use",
221827
- version: "2.5.2",
221827
+ version: "2.5.4",
221828
221828
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
221829
221829
  license: "Apache-2.0",
221830
221830
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -243134,7 +243134,7 @@ function acquireIdentityLock(params) {
243134
243134
  }
243135
243135
  lastHolderPid = existing.pid;
243136
243136
  const expired = params.nowMs() - existing.acquiredAtMs > staleAfterMs;
243137
- if (expired || !params.isProcessAlive(existing.pid)) {
243137
+ if (expired || !params.isRunning(existing.pid)) {
243138
243138
  params.fs.removeRecursive(lockPath);
243139
243139
  continue;
243140
243140
  }
@@ -243394,7 +243394,7 @@ function recoverFarm(params) {
243394
243394
  fs: params.fs,
243395
243395
  nowMs: params.now,
243396
243396
  pid: params.lock.pid,
243397
- isProcessAlive: params.lock.isProcessAlive,
243397
+ isRunning: params.lock.isRunning,
243398
243398
  sleep: params.lock.sleep,
243399
243399
  ...params.lock.staleAfterMs === void 0 ? {} : { staleAfterMs: params.lock.staleAfterMs },
243400
243400
  ...params.lock.retryDelayMs === void 0 ? {} : { retryDelayMs: params.lock.retryDelayMs },
@@ -243487,7 +243487,7 @@ function resyncFarm(params) {
243487
243487
  fs: params.fs,
243488
243488
  nowMs: params.now,
243489
243489
  pid: params.lock.pid,
243490
- isProcessAlive: params.lock.isProcessAlive,
243490
+ isRunning: params.lock.isRunning,
243491
243491
  sleep: params.lock.sleep,
243492
243492
  ...params.lock.staleAfterMs === void 0 ? {} : { staleAfterMs: params.lock.staleAfterMs },
243493
243493
  ...params.lock.retryDelayMs === void 0 ? {} : { retryDelayMs: params.lock.retryDelayMs },
@@ -243749,6 +243749,7 @@ function resolveLayoutPaths() {
243749
243749
  var import_node_child_process2 = require("node:child_process");
243750
243750
  var import_node_crypto4 = require("node:crypto");
243751
243751
  var import_node_fs3 = __toESM(require("node:fs"), 1);
243752
+ var import_node_net = __toESM(require("node:net"), 1);
243752
243753
  var import_node_path13 = __toESM(require("node:path"), 1);
243753
243754
  var import_node_sea = require("node:sea");
243754
243755
 
@@ -243829,6 +243830,10 @@ var HeadroomStateSchema = external_exports.strictObject({
243829
243830
  headroomPid: external_exports.number().int().positive().optional(),
243830
243831
  /** The loopback port the proxy listens on. Absent until the proxy has passed its readiness check, so "port is set" is itself the ready signal a launcher polls for. */
243831
243832
  port: external_exports.number().int().positive().optional(),
243833
+ /**
243834
+ * The sticky port preference: the address the daemon last served on, kept across crashes, restarts, and idle shutdowns so the next start reuses it. Distinct from `port` on purpose: `port` is the ready signal (absent whenever nothing is serving), while `lastPort` survives every shutdown, because every live session's environment was frozen at launch pointing at this address and a restart that moves strands them.
243835
+ */
243836
+ lastPort: external_exports.number().int().positive().optional(),
243832
243837
  /** The `headroom --version` output of the running install. */
243833
243838
  version: external_exports.string().optional(),
243834
243839
  /** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
@@ -243911,10 +243916,10 @@ function listSessions(fs11, sessionsDir) {
243911
243916
  }
243912
243917
  return sessions.sort((a2, b) => a2.pid - b.pid);
243913
243918
  }
243914
- function pruneDeadSessions(fs11, sessionsDir, isProcessAlive) {
243919
+ function pruneDeadSessions(fs11, sessionsDir, isRunning) {
243915
243920
  const removed = [];
243916
243921
  for (const session of listSessions(fs11, sessionsDir)) {
243917
- if (!isProcessAlive(session.pid)) {
243922
+ if (!isRunning(session.pid)) {
243918
243923
  removeSession(fs11, sessionsDir, session.pid);
243919
243924
  removed.push(session.pid);
243920
243925
  }
@@ -243944,8 +243949,8 @@ function ensureHeadroom(params) {
243944
243949
  let spawnedSupervisor = false;
243945
243950
  for (; ; ) {
243946
243951
  const state = readHeadroomState(ports.fs, paths.headroomStateFile);
243947
- if (state?.supervisorPid !== void 0 && ports.isProcessAlive(state.supervisorPid)) {
243948
- const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isProcessAlive(state.headroomPid);
243952
+ if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
243953
+ const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
243949
243954
  if (daemonUp && state.port !== void 0) {
243950
243955
  writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
243951
243956
  return { port: state.port };
@@ -243967,7 +243972,7 @@ function ensureHeadroom(params) {
243967
243972
  spawnedSupervisor = true;
243968
243973
  } else {
243969
243974
  const lock = readStartLock(ports.fs, paths.headroomLockFile);
243970
- if (lock === void 0 || !ports.isProcessAlive(lock.pid)) {
243975
+ if (lock === void 0 || !ports.isRunning(lock.pid)) {
243971
243976
  ports.fs.removeRecursive(paths.headroomLockFile);
243972
243977
  continue;
243973
243978
  }
@@ -244099,6 +244104,13 @@ function realIsProcessAlive(pid) {
244099
244104
  return isErrorWithCode(error62, "EPERM");
244100
244105
  }
244101
244106
  }
244107
+ function realIsProcessZombie(pid) {
244108
+ const result = (0, import_node_child_process2.spawnSync)("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
244109
+ return result.status === 0 && result.stdout.trim().startsWith("Z");
244110
+ }
244111
+ function realIsProcessRunning(pid) {
244112
+ return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
244113
+ }
244102
244114
  var realRunPort = {
244103
244115
  run(command, args) {
244104
244116
  const result = (0, import_node_child_process2.spawnSync)(command, [...args], { encoding: "utf8" });
@@ -244146,6 +244158,21 @@ function spawnHeadroomSupervisor(paths) {
244146
244158
  import_node_fs3.default.closeSync(logFd);
244147
244159
  }
244148
244160
  }
244161
+ async function realIsPortFree(port) {
244162
+ return await new Promise((resolve) => {
244163
+ const server2 = import_node_net.default.createServer();
244164
+ server2.unref();
244165
+ server2.once("error", () => {
244166
+ resolve(false);
244167
+ });
244168
+ server2.once("close", () => {
244169
+ resolve(true);
244170
+ });
244171
+ server2.listen(port, "127.0.0.1", () => {
244172
+ server2.close();
244173
+ });
244174
+ });
244175
+ }
244149
244176
  function realHeadroomPort(paths) {
244150
244177
  return {
244151
244178
  ensure: () => {
@@ -244154,7 +244181,8 @@ function realHeadroomPort(paths) {
244154
244181
  launcherPid: process.pid,
244155
244182
  ports: {
244156
244183
  fs: realFarmFs,
244157
- isProcessAlive: realIsProcessAlive,
244184
+ // Zombie-aware on purpose: a supervisor that died while still a child of this launcher sits unreaped until the launcher itself exits, and a defunct supervisor answering signal 0 as alive would stretch every launch to the full start timeout.
244185
+ isRunning: realIsProcessRunning,
244158
244186
  now: () => Date.now(),
244159
244187
  sleep: realSleepSync,
244160
244188
  spawnSupervisor: spawnHeadroomSupervisor
@@ -247223,8 +247251,8 @@ function runDoctor(params) {
247223
247251
  push("headroom", "fail", validated.message);
247224
247252
  } else {
247225
247253
  const state = validated.data;
247226
- const supervisorAlive = state.supervisorPid !== void 0 && params.headroom.isProcessAlive(state.supervisorPid);
247227
- const headroomAlive = state.headroomPid !== void 0 && params.headroom.isProcessAlive(state.headroomPid);
247254
+ const supervisorAlive = state.supervisorPid !== void 0 && params.headroom.isRunning(state.supervisorPid);
247255
+ const headroomAlive = state.headroomPid !== void 0 && params.headroom.isRunning(state.headroomPid);
247228
247256
  if (state.supervisorPid === void 0) {
247229
247257
  if (state.lastError === void 0) {
247230
247258
  push("headroom", "pass", "Headroom daemon is stopped (idle shutdown) with no recorded error.");
@@ -247400,7 +247428,7 @@ function registerDoctorCommand(program2, paths) {
247400
247428
  },
247401
247429
  run: realRunPort,
247402
247430
  platform: process.platform,
247403
- headroom: { state: { path: paths.headroomStateFile, raw: realFsPort.readFileUtf8(paths.headroomStateFile) }, isProcessAlive: realIsProcessAlive }
247431
+ headroom: { state: { path: paths.headroomStateFile, raw: realFsPort.readFileUtf8(paths.headroomStateFile) }, isRunning: realIsProcessRunning }
247404
247432
  });
247405
247433
  for (const line of formatDoctorReport(report)) {
247406
247434
  console.log(line);
@@ -247413,7 +247441,7 @@ function registerDoctorCommand(program2, paths) {
247413
247441
 
247414
247442
  // src/headroom/commands.ts
247415
247443
  var import_node_fs9 = __toESM(require("node:fs"), 1);
247416
- var import_node_net = __toESM(require("node:net"), 1);
247444
+ var import_node_net2 = __toESM(require("node:net"), 1);
247417
247445
  var import_node_child_process3 = require("node:child_process");
247418
247446
 
247419
247447
  // src/headroom/supervisor.ts
@@ -247429,6 +247457,36 @@ var HEADROOM_START_RETRY_BUDGET = 5;
247429
247457
  var HEADROOM_READY_TIMEOUT_MS = 15e3;
247430
247458
  var HEADROOM_POLL_MS = 1e3;
247431
247459
  var MS_PER_MINUTE = 6e4;
247460
+ var STOP_POLL_MS = 100;
247461
+ var TERM_GRACE_MS = 1500;
247462
+ var KILL_GRACE_MS = 1500;
247463
+ function stopSupervisedProcess(pid, primitives) {
247464
+ const send = (signal) => {
247465
+ try {
247466
+ primitives.signal(pid, signal);
247467
+ } catch {
247468
+ }
247469
+ };
247470
+ send("SIGTERM");
247471
+ if (awaitExit(pid, primitives, TERM_GRACE_MS)) {
247472
+ return "exited-on-term";
247473
+ }
247474
+ send("SIGKILL");
247475
+ if (awaitExit(pid, primitives, KILL_GRACE_MS)) {
247476
+ return "killed";
247477
+ }
247478
+ return "still-running";
247479
+ }
247480
+ function awaitExit(pid, primitives, budgetMs) {
247481
+ const deadline = primitives.now() + budgetMs;
247482
+ while (primitives.isRunning(pid)) {
247483
+ if (primitives.now() >= deadline) {
247484
+ return false;
247485
+ }
247486
+ primitives.waitMs(STOP_POLL_MS);
247487
+ }
247488
+ return true;
247489
+ }
247432
247490
  function backoffForAttempt(attempt) {
247433
247491
  return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
247434
247492
  }
@@ -247485,13 +247543,18 @@ function compareVersions2(a2, b) {
247485
247543
  var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
247486
247544
  async function runSupervisor(config2, ports, options = {}) {
247487
247545
  const { fs: fs11, paths } = ports;
247546
+ const previousState = readHeadroomState(fs11, paths.headroomStateFile);
247547
+ let installedSource = previousState?.installedSource;
247548
+ let lastPort = previousState?.lastPort;
247488
247549
  const fail = (message) => {
247489
- writeHeadroomState(fs11, paths.headroomStateFile, { lastError: message });
247550
+ writeHeadroomState(fs11, paths.headroomStateFile, {
247551
+ lastError: message,
247552
+ ...lastPort === void 0 ? {} : { lastPort }
247553
+ });
247490
247554
  fs11.removeRecursive(paths.headroomLockFile);
247491
247555
  ports.log(`claude-use headroom supervisor: ${message}`);
247492
247556
  return 1;
247493
247557
  };
247494
- let installedSource = readHeadroomState(fs11, paths.headroomStateFile)?.installedSource;
247495
247558
  const ensureInstalled = () => {
247496
247559
  const version3 = ports.headroomVersion();
247497
247560
  const versionOk = version3 !== void 0 && versionSatisfies(version3, config2.source);
@@ -247511,8 +247574,13 @@ async function runSupervisor(config2, ports, options = {}) {
247511
247574
  return fail(`could not install headroom from ${config2.source}: install failed (is uv installed and on PATH?)`);
247512
247575
  }
247513
247576
  const version2 = ports.headroomVersion() ?? "unknown";
247514
- writeHeadroomState(fs11, paths.headroomStateFile, { supervisorPid: ports.ownPid, version: version2, installedSource });
247577
+ writeHeadroomState(fs11, paths.headroomStateFile, { supervisorPid: ports.ownPid, version: version2, installedSource, ...lastPort === void 0 ? {} : { lastPort } });
247515
247578
  ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
247579
+ const orphanPid = previousState?.headroomPid;
247580
+ if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
247581
+ ports.log(`claude-use headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
247582
+ ports.stopProcess(orphanPid);
247583
+ }
247516
247584
  let headroomPid;
247517
247585
  let runningHash;
247518
247586
  let consecutiveFailures = 0;
@@ -247525,13 +247593,19 @@ async function runSupervisor(config2, ports, options = {}) {
247525
247593
  ticks += 1;
247526
247594
  const allowlist = allowlistOf(ports);
247527
247595
  const allowlistHash = hashAllowlist(allowlist);
247528
- pruneDeadSessions(fs11, paths.headroomSessionsDir, ports.isProcessAlive);
247529
- const crashed = headroomPid !== void 0 && !ports.isProcessAlive(headroomPid);
247596
+ pruneDeadSessions(fs11, paths.headroomSessionsDir, ports.isRunning);
247597
+ const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
247530
247598
  if (headroomPid === void 0 || crashed) {
247531
247599
  if (crashed) {
247532
247600
  ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
247533
247601
  headroomPid = void 0;
247534
247602
  runningHash = void 0;
247603
+ writeHeadroomState(fs11, paths.headroomStateFile, {
247604
+ supervisorPid: ports.ownPid,
247605
+ version: version2,
247606
+ installedSource,
247607
+ ...lastPort === void 0 ? {} : { lastPort }
247608
+ });
247535
247609
  }
247536
247610
  if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
247537
247611
  return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
@@ -247539,17 +247613,25 @@ async function runSupervisor(config2, ports, options = {}) {
247539
247613
  if (installedSource !== config2.source && !ensureInstalled()) {
247540
247614
  return fail(`could not install headroom from ${config2.source}: install failed (is uv installed and on PATH?)`);
247541
247615
  }
247542
- const port = await ports.freePort();
247616
+ const previousSticky = lastPort;
247617
+ const port = lastPort !== void 0 && await ports.isPortFree(lastPort) ? lastPort : await ports.freePort();
247543
247618
  const pid = ports.spawnHeadroom(port, allowlist);
247544
247619
  const ready = await waitUntilReady(ports, port);
247545
247620
  if (ready) {
247546
247621
  consecutiveFailures = 0;
247547
247622
  headroomPid = pid;
247548
247623
  runningHash = allowlistHash;
247624
+ lastPort = port;
247625
+ if (previousSticky !== void 0 && port !== previousSticky) {
247626
+ ports.log(
247627
+ `claude-use headroom supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(port)} (sessions launched against the old port are stale until they relaunch)`
247628
+ );
247629
+ }
247549
247630
  writeHeadroomState(fs11, paths.headroomStateFile, {
247550
247631
  supervisorPid: ports.ownPid,
247551
247632
  headroomPid: pid,
247552
247633
  port,
247634
+ lastPort: port,
247553
247635
  version: version2,
247554
247636
  allowlistHash,
247555
247637
  installedSource: config2.source
@@ -247563,7 +247645,7 @@ async function runSupervisor(config2, ports, options = {}) {
247563
247645
  ports.log(
247564
247646
  `claude-use headroom supervisor: headroom did not become ready on port ${String(port)} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
247565
247647
  );
247566
- ports.sleep(backoffForAttempt(consecutiveFailures));
247648
+ await ports.sleep(backoffForAttempt(consecutiveFailures));
247567
247649
  continue;
247568
247650
  }
247569
247651
  } else {
@@ -247585,14 +247667,17 @@ async function runSupervisor(config2, ports, options = {}) {
247585
247667
  `claude-use headroom supervisor: no sessions for ${String(config2.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
247586
247668
  );
247587
247669
  ports.stopProcess(headroomPid);
247588
- writeHeadroomState(fs11, paths.headroomStateFile, { version: version2 });
247670
+ writeHeadroomState(fs11, paths.headroomStateFile, {
247671
+ version: version2,
247672
+ ...lastPort === void 0 ? {} : { lastPort }
247673
+ });
247589
247674
  return 0;
247590
247675
  }
247591
247676
  } else {
247592
247677
  idleSince = void 0;
247593
247678
  }
247594
247679
  }
247595
- ports.sleep(HEADROOM_POLL_MS);
247680
+ await ports.sleep(HEADROOM_POLL_MS);
247596
247681
  }
247597
247682
  }
247598
247683
  function allowlistOf(ports) {
@@ -247607,21 +247692,21 @@ async function waitUntilReady(ports, port) {
247607
247692
  if (ports.now() >= deadline) {
247608
247693
  return false;
247609
247694
  }
247610
- ports.sleep(HEADROOM_POLL_MS);
247695
+ await ports.sleep(HEADROOM_POLL_MS);
247611
247696
  }
247612
247697
  }
247613
247698
 
247614
247699
  // src/headroom/commands.ts
247615
- function collectHeadroomStatus(fsPort, paths, isProcessAlive) {
247700
+ function collectHeadroomStatus(fsPort, paths, isRunning) {
247616
247701
  const state = readHeadroomState(fsPort, paths.headroomStateFile) ?? {};
247617
247702
  const allowlist = headroomAllowlist(readAllProviders(fsPort, paths.providersDir).map((entry) => entry.provider));
247618
247703
  return {
247619
247704
  state,
247620
- supervisorAlive: state.supervisorPid !== void 0 && isProcessAlive(state.supervisorPid),
247621
- headroomAlive: state.headroomPid !== void 0 && isProcessAlive(state.headroomPid),
247705
+ supervisorAlive: state.supervisorPid !== void 0 && isRunning(state.supervisorPid),
247706
+ headroomAlive: state.headroomPid !== void 0 && isRunning(state.headroomPid),
247622
247707
  sessions: listSessions(fsPort, paths.headroomSessionsDir).map((session) => ({
247623
247708
  ...session,
247624
- alive: isProcessAlive(session.pid)
247709
+ alive: isRunning(session.pid)
247625
247710
  })),
247626
247711
  allowlist,
247627
247712
  allowlistDrifted: state.allowlistHash !== void 0 && state.allowlistHash !== hashAllowlist(allowlist),
@@ -247661,7 +247746,7 @@ function formatHeadroomStatus(status) {
247661
247746
  }
247662
247747
  async function realFreePort() {
247663
247748
  return await new Promise((resolve, reject) => {
247664
- const server2 = import_node_net.default.createServer();
247749
+ const server2 = import_node_net2.default.createServer();
247665
247750
  server2.unref();
247666
247751
  server2.on("error", reject);
247667
247752
  server2.listen(0, "127.0.0.1", () => {
@@ -247677,18 +247762,27 @@ async function realFreePort() {
247677
247762
  });
247678
247763
  });
247679
247764
  }
247680
- var supervisedHeadroomPid;
247681
- var STOP_GRACE_MS = 1500;
247765
+ var ownedHeadroom = /* @__PURE__ */ new Map();
247766
+ var exitedHeadroom = /* @__PURE__ */ new Set();
247682
247767
  var READY_FETCH_TIMEOUT_MS = 2e3;
247768
+ function headroomPidRunning(pid) {
247769
+ return !exitedHeadroom.has(pid) && realIsProcessRunning(pid);
247770
+ }
247683
247771
  function realSupervisorPorts(paths) {
247684
247772
  return {
247685
247773
  fs: realFarmFs,
247686
247774
  paths,
247687
247775
  ownPid: process.pid,
247688
247776
  now: () => Date.now(),
247689
- sleep: realSleepSync,
247690
- isProcessAlive: realIsProcessAlive,
247777
+ // A real timer, not the launcher's blocking Atomics.wait: the supervisor lives on its event loop, and the child's exit event can only be delivered while the loop turns.
247778
+ sleep: async (ms) => {
247779
+ await new Promise((resolve) => {
247780
+ setTimeout(resolve, ms);
247781
+ });
247782
+ },
247783
+ isRunning: headroomPidRunning,
247691
247784
  freePort: realFreePort,
247785
+ isPortFree: realIsPortFree,
247692
247786
  spawnHeadroom: (port, allowlist) => {
247693
247787
  import_node_fs9.default.mkdirSync(paths.logsDir, { recursive: true });
247694
247788
  const logFd = import_node_fs9.default.openSync(paths.headroomLogPath, "a");
@@ -247698,28 +247792,37 @@ function realSupervisorPorts(paths) {
247698
247792
  stdio: ["ignore", logFd, logFd],
247699
247793
  env: { ...process.env, HEADROOM_ALLOWED_BASE_URLS: allowlist.join(",") }
247700
247794
  });
247701
- child.unref();
247702
247795
  if (child.pid === void 0) {
247703
247796
  throw new Error("spawning headroom returned no pid");
247704
247797
  }
247705
- supervisedHeadroomPid = child.pid;
247706
- return child.pid;
247798
+ const pid = child.pid;
247799
+ ownedHeadroom.set(pid, child);
247800
+ child.once("exit", (code, signal) => {
247801
+ ownedHeadroom.delete(pid);
247802
+ exitedHeadroom.add(pid);
247803
+ import_node_fs9.default.appendFileSync(paths.headroomLogPath, `${(/* @__PURE__ */ new Date()).toISOString()} claude-use headroom supervisor: headroom exited (${signal ?? `code ${String(code)}`})
247804
+ `);
247805
+ });
247806
+ return pid;
247707
247807
  } finally {
247708
247808
  import_node_fs9.default.closeSync(logFd);
247709
247809
  }
247710
247810
  },
247711
247811
  stopProcess: (pid) => {
247712
- try {
247713
- process.kill(pid, "SIGTERM");
247714
- } catch {
247715
- }
247716
- realSleepSync(STOP_GRACE_MS);
247717
- try {
247718
- process.kill(pid, "SIGKILL");
247719
- } catch {
247720
- }
247721
- if (supervisedHeadroomPid === pid) {
247722
- supervisedHeadroomPid = void 0;
247812
+ const outcome = stopSupervisedProcess(pid, {
247813
+ signal: (target, signal) => {
247814
+ process.kill(target, signal);
247815
+ },
247816
+ isRunning: headroomPidRunning,
247817
+ waitMs: realSleepSync,
247818
+ now: () => Date.now()
247819
+ });
247820
+ if (outcome === "still-running") {
247821
+ import_node_fs9.default.appendFileSync(
247822
+ paths.headroomLogPath,
247823
+ `${(/* @__PURE__ */ new Date()).toISOString()} claude-use headroom supervisor: pid ${String(pid)} survived SIGKILL within its grace; it is stuck uninterruptibly
247824
+ `
247825
+ );
247723
247826
  }
247724
247827
  },
247725
247828
  ready: async (port) => {
@@ -247757,7 +247860,7 @@ function realSupervisorPorts(paths) {
247757
247860
  function registerHeadroomCommand(program2, paths) {
247758
247861
  const headroom = program2.command("headroom").description("Inspect the headroom routing daemon.");
247759
247862
  headroom.command("status").description("Report the headroom daemon's supervisor, process, port, sessions, and last error. Read-only.").action(() => {
247760
- for (const line of formatHeadroomStatus(collectHeadroomStatus(realFarmFs, paths, realIsProcessAlive))) {
247863
+ for (const line of formatHeadroomStatus(collectHeadroomStatus(realFarmFs, paths, realIsProcessRunning))) {
247761
247864
  console.log(line);
247762
247865
  }
247763
247866
  });
@@ -247765,13 +247868,19 @@ function registerHeadroomCommand(program2, paths) {
247765
247868
  const globalConfig2 = readGlobalConfig(paths);
247766
247869
  const config2 = resolveSupervisorConfig(globalConfig2?.headroom ?? {});
247767
247870
  process.on("exit", () => {
247768
- if (supervisedHeadroomPid !== void 0) {
247871
+ for (const pid of ownedHeadroom.keys()) {
247769
247872
  try {
247770
- process.kill(supervisedHeadroomPid, "SIGTERM");
247873
+ process.kill(pid, "SIGKILL");
247771
247874
  } catch {
247772
247875
  }
247773
247876
  }
247774
247877
  });
247878
+ process.on("SIGTERM", () => {
247879
+ process.exit(0);
247880
+ });
247881
+ process.on("SIGINT", () => {
247882
+ process.exit(0);
247883
+ });
247775
247884
  const code = await runSupervisor(config2, realSupervisorPorts(paths));
247776
247885
  process.exit(code);
247777
247886
  });
@@ -248462,7 +248571,8 @@ function buildFarmRuntime(paths) {
248462
248571
  }).input,
248463
248572
  now: () => Date.now(),
248464
248573
  uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto5.randomUUID)()}`,
248465
- lock: { pid: process.pid, isProcessAlive: realIsProcessAlive, sleep: realSleepSync }
248574
+ // Zombie-aware on purpose: a previous launcher that crashed out of a resync without releasing the lock may sit unreaped, still answering signal 0 as alive, and must read as a dead holder so this launch takes the lock over instead of timing out.
248575
+ lock: { pid: process.pid, isRunning: realIsProcessRunning, sleep: realSleepSync }
248466
248576
  },
248467
248577
  ...selections.identity === void 0 ? {} : { directoryIdentity: selections.identity },
248468
248578
  ...selections.configProfile === void 0 ? {} : { directoryConfigProfile: selections.configProfile },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-use",
3
- "version": "2.5.2",
3
+ "version": "2.5.4",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",