claude-use 2.5.2 → 2.5.3

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 +114 -45
  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.3",
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 },
@@ -243911,10 +243911,10 @@ function listSessions(fs11, sessionsDir) {
243911
243911
  }
243912
243912
  return sessions.sort((a2, b) => a2.pid - b.pid);
243913
243913
  }
243914
- function pruneDeadSessions(fs11, sessionsDir, isProcessAlive) {
243914
+ function pruneDeadSessions(fs11, sessionsDir, isRunning) {
243915
243915
  const removed = [];
243916
243916
  for (const session of listSessions(fs11, sessionsDir)) {
243917
- if (!isProcessAlive(session.pid)) {
243917
+ if (!isRunning(session.pid)) {
243918
243918
  removeSession(fs11, sessionsDir, session.pid);
243919
243919
  removed.push(session.pid);
243920
243920
  }
@@ -243944,8 +243944,8 @@ function ensureHeadroom(params) {
243944
243944
  let spawnedSupervisor = false;
243945
243945
  for (; ; ) {
243946
243946
  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);
243947
+ if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
243948
+ const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
243949
243949
  if (daemonUp && state.port !== void 0) {
243950
243950
  writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
243951
243951
  return { port: state.port };
@@ -243967,7 +243967,7 @@ function ensureHeadroom(params) {
243967
243967
  spawnedSupervisor = true;
243968
243968
  } else {
243969
243969
  const lock = readStartLock(ports.fs, paths.headroomLockFile);
243970
- if (lock === void 0 || !ports.isProcessAlive(lock.pid)) {
243970
+ if (lock === void 0 || !ports.isRunning(lock.pid)) {
243971
243971
  ports.fs.removeRecursive(paths.headroomLockFile);
243972
243972
  continue;
243973
243973
  }
@@ -244099,6 +244099,13 @@ function realIsProcessAlive(pid) {
244099
244099
  return isErrorWithCode(error62, "EPERM");
244100
244100
  }
244101
244101
  }
244102
+ function realIsProcessZombie(pid) {
244103
+ const result = (0, import_node_child_process2.spawnSync)("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
244104
+ return result.status === 0 && result.stdout.trim().startsWith("Z");
244105
+ }
244106
+ function realIsProcessRunning(pid) {
244107
+ return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
244108
+ }
244102
244109
  var realRunPort = {
244103
244110
  run(command, args) {
244104
244111
  const result = (0, import_node_child_process2.spawnSync)(command, [...args], { encoding: "utf8" });
@@ -244154,7 +244161,8 @@ function realHeadroomPort(paths) {
244154
244161
  launcherPid: process.pid,
244155
244162
  ports: {
244156
244163
  fs: realFarmFs,
244157
- isProcessAlive: realIsProcessAlive,
244164
+ // 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.
244165
+ isRunning: realIsProcessRunning,
244158
244166
  now: () => Date.now(),
244159
244167
  sleep: realSleepSync,
244160
244168
  spawnSupervisor: spawnHeadroomSupervisor
@@ -247223,8 +247231,8 @@ function runDoctor(params) {
247223
247231
  push("headroom", "fail", validated.message);
247224
247232
  } else {
247225
247233
  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);
247234
+ const supervisorAlive = state.supervisorPid !== void 0 && params.headroom.isRunning(state.supervisorPid);
247235
+ const headroomAlive = state.headroomPid !== void 0 && params.headroom.isRunning(state.headroomPid);
247228
247236
  if (state.supervisorPid === void 0) {
247229
247237
  if (state.lastError === void 0) {
247230
247238
  push("headroom", "pass", "Headroom daemon is stopped (idle shutdown) with no recorded error.");
@@ -247400,7 +247408,7 @@ function registerDoctorCommand(program2, paths) {
247400
247408
  },
247401
247409
  run: realRunPort,
247402
247410
  platform: process.platform,
247403
- headroom: { state: { path: paths.headroomStateFile, raw: realFsPort.readFileUtf8(paths.headroomStateFile) }, isProcessAlive: realIsProcessAlive }
247411
+ headroom: { state: { path: paths.headroomStateFile, raw: realFsPort.readFileUtf8(paths.headroomStateFile) }, isRunning: realIsProcessRunning }
247404
247412
  });
247405
247413
  for (const line of formatDoctorReport(report)) {
247406
247414
  console.log(line);
@@ -247429,6 +247437,36 @@ var HEADROOM_START_RETRY_BUDGET = 5;
247429
247437
  var HEADROOM_READY_TIMEOUT_MS = 15e3;
247430
247438
  var HEADROOM_POLL_MS = 1e3;
247431
247439
  var MS_PER_MINUTE = 6e4;
247440
+ var STOP_POLL_MS = 100;
247441
+ var TERM_GRACE_MS = 1500;
247442
+ var KILL_GRACE_MS = 1500;
247443
+ function stopSupervisedProcess(pid, primitives) {
247444
+ const send = (signal) => {
247445
+ try {
247446
+ primitives.signal(pid, signal);
247447
+ } catch {
247448
+ }
247449
+ };
247450
+ send("SIGTERM");
247451
+ if (awaitExit(pid, primitives, TERM_GRACE_MS)) {
247452
+ return "exited-on-term";
247453
+ }
247454
+ send("SIGKILL");
247455
+ if (awaitExit(pid, primitives, KILL_GRACE_MS)) {
247456
+ return "killed";
247457
+ }
247458
+ return "still-running";
247459
+ }
247460
+ function awaitExit(pid, primitives, budgetMs) {
247461
+ const deadline = primitives.now() + budgetMs;
247462
+ while (primitives.isRunning(pid)) {
247463
+ if (primitives.now() >= deadline) {
247464
+ return false;
247465
+ }
247466
+ primitives.waitMs(STOP_POLL_MS);
247467
+ }
247468
+ return true;
247469
+ }
247432
247470
  function backoffForAttempt(attempt) {
247433
247471
  return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
247434
247472
  }
@@ -247491,7 +247529,8 @@ async function runSupervisor(config2, ports, options = {}) {
247491
247529
  ports.log(`claude-use headroom supervisor: ${message}`);
247492
247530
  return 1;
247493
247531
  };
247494
- let installedSource = readHeadroomState(fs11, paths.headroomStateFile)?.installedSource;
247532
+ const previousState = readHeadroomState(fs11, paths.headroomStateFile);
247533
+ let installedSource = previousState?.installedSource;
247495
247534
  const ensureInstalled = () => {
247496
247535
  const version3 = ports.headroomVersion();
247497
247536
  const versionOk = version3 !== void 0 && versionSatisfies(version3, config2.source);
@@ -247513,6 +247552,11 @@ async function runSupervisor(config2, ports, options = {}) {
247513
247552
  const version2 = ports.headroomVersion() ?? "unknown";
247514
247553
  writeHeadroomState(fs11, paths.headroomStateFile, { supervisorPid: ports.ownPid, version: version2, installedSource });
247515
247554
  ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
247555
+ const orphanPid = previousState?.headroomPid;
247556
+ if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
247557
+ ports.log(`claude-use headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
247558
+ ports.stopProcess(orphanPid);
247559
+ }
247516
247560
  let headroomPid;
247517
247561
  let runningHash;
247518
247562
  let consecutiveFailures = 0;
@@ -247525,13 +247569,14 @@ async function runSupervisor(config2, ports, options = {}) {
247525
247569
  ticks += 1;
247526
247570
  const allowlist = allowlistOf(ports);
247527
247571
  const allowlistHash = hashAllowlist(allowlist);
247528
- pruneDeadSessions(fs11, paths.headroomSessionsDir, ports.isProcessAlive);
247529
- const crashed = headroomPid !== void 0 && !ports.isProcessAlive(headroomPid);
247572
+ pruneDeadSessions(fs11, paths.headroomSessionsDir, ports.isRunning);
247573
+ const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
247530
247574
  if (headroomPid === void 0 || crashed) {
247531
247575
  if (crashed) {
247532
247576
  ports.log(`claude-use headroom supervisor: headroom pid ${String(headroomPid)} died`);
247533
247577
  headroomPid = void 0;
247534
247578
  runningHash = void 0;
247579
+ writeHeadroomState(fs11, paths.headroomStateFile, { supervisorPid: ports.ownPid, version: version2, installedSource });
247535
247580
  }
247536
247581
  if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
247537
247582
  return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
@@ -247563,7 +247608,7 @@ async function runSupervisor(config2, ports, options = {}) {
247563
247608
  ports.log(
247564
247609
  `claude-use headroom supervisor: headroom did not become ready on port ${String(port)} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
247565
247610
  );
247566
- ports.sleep(backoffForAttempt(consecutiveFailures));
247611
+ await ports.sleep(backoffForAttempt(consecutiveFailures));
247567
247612
  continue;
247568
247613
  }
247569
247614
  } else {
@@ -247592,7 +247637,7 @@ async function runSupervisor(config2, ports, options = {}) {
247592
247637
  idleSince = void 0;
247593
247638
  }
247594
247639
  }
247595
- ports.sleep(HEADROOM_POLL_MS);
247640
+ await ports.sleep(HEADROOM_POLL_MS);
247596
247641
  }
247597
247642
  }
247598
247643
  function allowlistOf(ports) {
@@ -247607,21 +247652,21 @@ async function waitUntilReady(ports, port) {
247607
247652
  if (ports.now() >= deadline) {
247608
247653
  return false;
247609
247654
  }
247610
- ports.sleep(HEADROOM_POLL_MS);
247655
+ await ports.sleep(HEADROOM_POLL_MS);
247611
247656
  }
247612
247657
  }
247613
247658
 
247614
247659
  // src/headroom/commands.ts
247615
- function collectHeadroomStatus(fsPort, paths, isProcessAlive) {
247660
+ function collectHeadroomStatus(fsPort, paths, isRunning) {
247616
247661
  const state = readHeadroomState(fsPort, paths.headroomStateFile) ?? {};
247617
247662
  const allowlist = headroomAllowlist(readAllProviders(fsPort, paths.providersDir).map((entry) => entry.provider));
247618
247663
  return {
247619
247664
  state,
247620
- supervisorAlive: state.supervisorPid !== void 0 && isProcessAlive(state.supervisorPid),
247621
- headroomAlive: state.headroomPid !== void 0 && isProcessAlive(state.headroomPid),
247665
+ supervisorAlive: state.supervisorPid !== void 0 && isRunning(state.supervisorPid),
247666
+ headroomAlive: state.headroomPid !== void 0 && isRunning(state.headroomPid),
247622
247667
  sessions: listSessions(fsPort, paths.headroomSessionsDir).map((session) => ({
247623
247668
  ...session,
247624
- alive: isProcessAlive(session.pid)
247669
+ alive: isRunning(session.pid)
247625
247670
  })),
247626
247671
  allowlist,
247627
247672
  allowlistDrifted: state.allowlistHash !== void 0 && state.allowlistHash !== hashAllowlist(allowlist),
@@ -247677,17 +247722,25 @@ async function realFreePort() {
247677
247722
  });
247678
247723
  });
247679
247724
  }
247680
- var supervisedHeadroomPid;
247681
- var STOP_GRACE_MS = 1500;
247725
+ var ownedHeadroom = /* @__PURE__ */ new Map();
247726
+ var exitedHeadroom = /* @__PURE__ */ new Set();
247682
247727
  var READY_FETCH_TIMEOUT_MS = 2e3;
247728
+ function headroomPidRunning(pid) {
247729
+ return !exitedHeadroom.has(pid) && realIsProcessRunning(pid);
247730
+ }
247683
247731
  function realSupervisorPorts(paths) {
247684
247732
  return {
247685
247733
  fs: realFarmFs,
247686
247734
  paths,
247687
247735
  ownPid: process.pid,
247688
247736
  now: () => Date.now(),
247689
- sleep: realSleepSync,
247690
- isProcessAlive: realIsProcessAlive,
247737
+ // 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.
247738
+ sleep: async (ms) => {
247739
+ await new Promise((resolve) => {
247740
+ setTimeout(resolve, ms);
247741
+ });
247742
+ },
247743
+ isRunning: headroomPidRunning,
247691
247744
  freePort: realFreePort,
247692
247745
  spawnHeadroom: (port, allowlist) => {
247693
247746
  import_node_fs9.default.mkdirSync(paths.logsDir, { recursive: true });
@@ -247698,28 +247751,37 @@ function realSupervisorPorts(paths) {
247698
247751
  stdio: ["ignore", logFd, logFd],
247699
247752
  env: { ...process.env, HEADROOM_ALLOWED_BASE_URLS: allowlist.join(",") }
247700
247753
  });
247701
- child.unref();
247702
247754
  if (child.pid === void 0) {
247703
247755
  throw new Error("spawning headroom returned no pid");
247704
247756
  }
247705
- supervisedHeadroomPid = child.pid;
247706
- return child.pid;
247757
+ const pid = child.pid;
247758
+ ownedHeadroom.set(pid, child);
247759
+ child.once("exit", (code, signal) => {
247760
+ ownedHeadroom.delete(pid);
247761
+ exitedHeadroom.add(pid);
247762
+ import_node_fs9.default.appendFileSync(paths.headroomLogPath, `${(/* @__PURE__ */ new Date()).toISOString()} claude-use headroom supervisor: headroom exited (${signal ?? `code ${String(code)}`})
247763
+ `);
247764
+ });
247765
+ return pid;
247707
247766
  } finally {
247708
247767
  import_node_fs9.default.closeSync(logFd);
247709
247768
  }
247710
247769
  },
247711
247770
  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;
247771
+ const outcome = stopSupervisedProcess(pid, {
247772
+ signal: (target, signal) => {
247773
+ process.kill(target, signal);
247774
+ },
247775
+ isRunning: headroomPidRunning,
247776
+ waitMs: realSleepSync,
247777
+ now: () => Date.now()
247778
+ });
247779
+ if (outcome === "still-running") {
247780
+ import_node_fs9.default.appendFileSync(
247781
+ paths.headroomLogPath,
247782
+ `${(/* @__PURE__ */ new Date()).toISOString()} claude-use headroom supervisor: pid ${String(pid)} survived SIGKILL within its grace; it is stuck uninterruptibly
247783
+ `
247784
+ );
247723
247785
  }
247724
247786
  },
247725
247787
  ready: async (port) => {
@@ -247757,7 +247819,7 @@ function realSupervisorPorts(paths) {
247757
247819
  function registerHeadroomCommand(program2, paths) {
247758
247820
  const headroom = program2.command("headroom").description("Inspect the headroom routing daemon.");
247759
247821
  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))) {
247822
+ for (const line of formatHeadroomStatus(collectHeadroomStatus(realFarmFs, paths, realIsProcessRunning))) {
247761
247823
  console.log(line);
247762
247824
  }
247763
247825
  });
@@ -247765,13 +247827,19 @@ function registerHeadroomCommand(program2, paths) {
247765
247827
  const globalConfig2 = readGlobalConfig(paths);
247766
247828
  const config2 = resolveSupervisorConfig(globalConfig2?.headroom ?? {});
247767
247829
  process.on("exit", () => {
247768
- if (supervisedHeadroomPid !== void 0) {
247830
+ for (const pid of ownedHeadroom.keys()) {
247769
247831
  try {
247770
- process.kill(supervisedHeadroomPid, "SIGTERM");
247832
+ process.kill(pid, "SIGKILL");
247771
247833
  } catch {
247772
247834
  }
247773
247835
  }
247774
247836
  });
247837
+ process.on("SIGTERM", () => {
247838
+ process.exit(0);
247839
+ });
247840
+ process.on("SIGINT", () => {
247841
+ process.exit(0);
247842
+ });
247775
247843
  const code = await runSupervisor(config2, realSupervisorPorts(paths));
247776
247844
  process.exit(code);
247777
247845
  });
@@ -248462,7 +248530,8 @@ function buildFarmRuntime(paths) {
248462
248530
  }).input,
248463
248531
  now: () => Date.now(),
248464
248532
  uniqueSuffix: `${String(process.pid)}.${(0, import_node_crypto5.randomUUID)()}`,
248465
- lock: { pid: process.pid, isProcessAlive: realIsProcessAlive, sleep: realSleepSync }
248533
+ // 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.
248534
+ lock: { pid: process.pid, isRunning: realIsProcessRunning, sleep: realSleepSync }
248466
248535
  },
248467
248536
  ...selections.identity === void 0 ? {} : { directoryIdentity: selections.identity },
248468
248537
  ...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.3",
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>",