claude-use 6.2.0 → 6.2.1

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/dist/cli.cjs CHANGED
@@ -55390,7 +55390,7 @@ var HeadroomStateSchema = external_exports.strictObject({
55390
55390
  /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
55391
55391
  lastError: external_exports.string().optional()
55392
55392
  });
55393
- var HeadroomSessionSchema = external_exports.strictObject({ pid: external_exports.number().int().positive(), startedAt: external_exports.number() });
55393
+ var HeadroomSessionSchema = external_exports.strictObject({ pid: external_exports.number().int().positive(), startedAt: external_exports.number(), supervisorPid: external_exports.number().int().positive() });
55394
55394
  var HeadroomLockSchema = external_exports.strictObject({ pid: external_exports.number().int().positive(), at: external_exports.number() });
55395
55395
  function parseJson(raw, schema) {
55396
55396
  if (raw === void 0 || raw.trim() === "") {
@@ -55470,6 +55470,9 @@ function listSessions(fs16, sessionsDir) {
55470
55470
  }
55471
55471
  return sessions.sort((a2, b) => a2.pid - b.pid);
55472
55472
  }
55473
+ function sessionsForSupervisor(fs16, sessionsDir, supervisorPid) {
55474
+ return listSessions(fs16, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
55475
+ }
55473
55476
  function pruneDeadSessions(fs16, sessionsDir, isRunning) {
55474
55477
  const removed = [];
55475
55478
  for (const session of listSessions(fs16, sessionsDir)) {
@@ -55506,7 +55509,7 @@ function ensureHeadroom(params) {
55506
55509
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
55507
55510
  const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
55508
55511
  if (daemonUp && state.port !== void 0) {
55509
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
55512
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
55510
55513
  return { port: state.port };
55511
55514
  }
55512
55515
  if (state.lastError !== void 0) {
@@ -60994,7 +60997,7 @@ function resolveLaunchIdentity(paths, cwd) {
60994
60997
  // package.json
60995
60998
  var package_default = {
60996
60999
  name: "claude-use",
60997
- version: "6.2.0",
61000
+ version: "6.2.1",
60998
61001
  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.",
60999
61002
  license: "Apache-2.0",
61000
61003
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -68074,15 +68077,28 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68074
68077
  const { fs: fs16, paths } = ports;
68075
68078
  let config2 = initialConfig;
68076
68079
  const previousState = readHeadroomState(fs16, paths.headroomStateFile);
68080
+ if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
68081
+ ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
68082
+ return 0;
68083
+ }
68077
68084
  let installedSource = previousState?.installedSource;
68078
68085
  let lastPort = previousState?.lastPort;
68079
68086
  const sticky = () => lastPort === void 0 ? {} : { lastPort };
68087
+ const ownsState = () => {
68088
+ const owner = readHeadroomState(fs16, paths.headroomStateFile)?.supervisorPid;
68089
+ return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
68090
+ };
68091
+ const writeOwnState = (state) => {
68092
+ if (!ownsState()) {
68093
+ return false;
68094
+ }
68095
+ writeHeadroomState(fs16, paths.headroomStateFile, state);
68096
+ return true;
68097
+ };
68080
68098
  const fail = (message) => {
68081
- writeHeadroomState(fs16, paths.headroomStateFile, {
68082
- lastError: message,
68083
- ...sticky()
68084
- });
68085
- fs16.removeRecursive(paths.headroomLockFile);
68099
+ if (writeOwnState({ lastError: message, ...sticky() })) {
68100
+ fs16.removeRecursive(paths.headroomLockFile);
68101
+ }
68086
68102
  ports.log(`claude-use headroom supervisor: ${message}`);
68087
68103
  return 1;
68088
68104
  };
@@ -68105,7 +68121,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68105
68121
  return fail(`could not install headroom from ${config2.source}: install failed (is uv installed and on PATH?)`);
68106
68122
  }
68107
68123
  const version2 = ports.headroomVersion() ?? "unknown";
68108
- writeHeadroomState(fs16, paths.headroomStateFile, { supervisorPid: ports.ownPid, version: version2, installedSource, ...sticky() });
68124
+ writeOwnState({ supervisorPid: ports.ownPid, version: version2, installedSource, ...sticky() });
68109
68125
  ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
68110
68126
  const orphanPid = previousState?.headroomPid;
68111
68127
  if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
@@ -68117,6 +68133,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68117
68133
  let runningSettingsHash;
68118
68134
  let consecutiveFailures = 0;
68119
68135
  let idleSince;
68136
+ let superseded = false;
68120
68137
  let ticks = 0;
68121
68138
  for (; ; ) {
68122
68139
  if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
@@ -68128,6 +68145,11 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68128
68145
  const allowlist = allowlistOf(ports);
68129
68146
  const allowlistHash = hashAllowlist(allowlist);
68130
68147
  pruneDeadSessions(fs16, paths.headroomSessionsDir, ports.isRunning);
68148
+ const ownsNow = ownsState();
68149
+ if (!ownsNow && !superseded) {
68150
+ ports.log("claude-use headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
68151
+ }
68152
+ superseded = !ownsNow;
68131
68153
  const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
68132
68154
  if (headroomPid === void 0 || crashed) {
68133
68155
  if (crashed) {
@@ -68135,7 +68157,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68135
68157
  headroomPid = void 0;
68136
68158
  runningHash = void 0;
68137
68159
  runningSettingsHash = void 0;
68138
- writeHeadroomState(fs16, paths.headroomStateFile, {
68160
+ writeOwnState({
68139
68161
  supervisorPid: ports.ownPid,
68140
68162
  version: version2,
68141
68163
  installedSource,
@@ -68163,7 +68185,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68163
68185
  `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)`
68164
68186
  );
68165
68187
  }
68166
- writeHeadroomState(fs16, paths.headroomStateFile, {
68188
+ const recorded = writeOwnState({
68167
68189
  supervisorPid: ports.ownPid,
68168
68190
  headroomPid: pid,
68169
68191
  port,
@@ -68173,7 +68195,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68173
68195
  installedSource: config2.source,
68174
68196
  ...sticky()
68175
68197
  });
68176
- fs16.removeRecursive(paths.headroomLockFile);
68198
+ if (recorded) {
68199
+ fs16.removeRecursive(paths.headroomLockFile);
68200
+ }
68177
68201
  idleSince = void 0;
68178
68202
  ports.log(`claude-use headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
68179
68203
  } else {
@@ -68187,7 +68211,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68187
68211
  }
68188
68212
  } else {
68189
68213
  if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config2.source) {
68190
- if (listSessions(fs16, paths.headroomSessionsDir).length === 0) {
68214
+ if (sessionsForSupervisor(fs16, paths.headroomSessionsDir, ports.ownPid).length === 0) {
68191
68215
  ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
68192
68216
  ports.stopProcess(headroomPid);
68193
68217
  headroomPid = void 0;
@@ -68196,7 +68220,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68196
68220
  continue;
68197
68221
  }
68198
68222
  }
68199
- const sessions = listSessions(fs16, paths.headroomSessionsDir);
68223
+ const sessions = sessionsForSupervisor(fs16, paths.headroomSessionsDir, ports.ownPid);
68200
68224
  if (sessions.length === 0) {
68201
68225
  idleSince ??= ports.now();
68202
68226
  const idleMs = ports.now() - idleSince;
@@ -68205,7 +68229,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
68205
68229
  `claude-use headroom supervisor: no sessions for ${String(config2.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
68206
68230
  );
68207
68231
  ports.stopProcess(headroomPid);
68208
- writeHeadroomState(fs16, paths.headroomStateFile, {
68232
+ writeOwnState({
68209
68233
  version: version2,
68210
68234
  ...sticky()
68211
68235
  });
@@ -68283,7 +68307,9 @@ function formatHeadroomStatus(status) {
68283
68307
  if (status.sessions.length === 0) {
68284
68308
  lines.push("sessions: none");
68285
68309
  } else {
68286
- const pids = status.sessions.map((session) => `${String(session.pid)}${session.alive ? "" : " (dead)"}`);
68310
+ const pids = status.sessions.map(
68311
+ (session) => `${String(session.pid)}${session.alive ? "" : " (dead)"}` + (session.supervisorPid === status.state.supervisorPid ? "" : ` on superseded supervisor ${String(session.supervisorPid)}`)
68312
+ );
68287
68313
  lines.push(`sessions: ${String(status.sessions.length)} registered (${pids.join(", ")})`);
68288
68314
  }
68289
68315
  if (status.state.lastError !== void 0) {
package/dist/index.cjs CHANGED
@@ -2811,7 +2811,7 @@ var HeadroomStateSchema = import_zod4.z.strictObject({
2811
2811
  /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
2812
2812
  lastError: import_zod4.z.string().optional()
2813
2813
  });
2814
- var HeadroomSessionSchema = import_zod4.z.strictObject({ pid: import_zod4.z.number().int().positive(), startedAt: import_zod4.z.number() });
2814
+ var HeadroomSessionSchema = import_zod4.z.strictObject({ pid: import_zod4.z.number().int().positive(), startedAt: import_zod4.z.number(), supervisorPid: import_zod4.z.number().int().positive() });
2815
2815
  var HeadroomLockSchema = import_zod4.z.strictObject({ pid: import_zod4.z.number().int().positive(), at: import_zod4.z.number() });
2816
2816
  function parseJson(raw, schema) {
2817
2817
  if (raw === void 0 || raw.trim() === "") {
@@ -2891,6 +2891,9 @@ function listSessions(fs2, sessionsDir) {
2891
2891
  }
2892
2892
  return sessions.sort((a, b) => a.pid - b.pid);
2893
2893
  }
2894
+ function sessionsForSupervisor(fs2, sessionsDir, supervisorPid) {
2895
+ return listSessions(fs2, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
2896
+ }
2894
2897
  function pruneDeadSessions(fs2, sessionsDir, isRunning) {
2895
2898
  const removed = [];
2896
2899
  for (const session of listSessions(fs2, sessionsDir)) {
@@ -3190,7 +3193,7 @@ function ensureHeadroom(params) {
3190
3193
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
3191
3194
  const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
3192
3195
  if (daemonUp && state.port !== void 0) {
3193
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
3196
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
3194
3197
  return { port: state.port };
3195
3198
  }
3196
3199
  if (state.lastError !== void 0) {
@@ -3327,15 +3330,28 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3327
3330
  const { fs: fs2, paths } = ports;
3328
3331
  let config = initialConfig;
3329
3332
  const previousState = readHeadroomState(fs2, paths.headroomStateFile);
3333
+ if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
3334
+ ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
3335
+ return 0;
3336
+ }
3330
3337
  let installedSource = previousState?.installedSource;
3331
3338
  let lastPort = previousState?.lastPort;
3332
3339
  const sticky = () => lastPort === void 0 ? {} : { lastPort };
3340
+ const ownsState = () => {
3341
+ const owner = readHeadroomState(fs2, paths.headroomStateFile)?.supervisorPid;
3342
+ return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
3343
+ };
3344
+ const writeOwnState = (state) => {
3345
+ if (!ownsState()) {
3346
+ return false;
3347
+ }
3348
+ writeHeadroomState(fs2, paths.headroomStateFile, state);
3349
+ return true;
3350
+ };
3333
3351
  const fail = (message) => {
3334
- writeHeadroomState(fs2, paths.headroomStateFile, {
3335
- lastError: message,
3336
- ...sticky()
3337
- });
3338
- fs2.removeRecursive(paths.headroomLockFile);
3352
+ if (writeOwnState({ lastError: message, ...sticky() })) {
3353
+ fs2.removeRecursive(paths.headroomLockFile);
3354
+ }
3339
3355
  ports.log(`claude-use headroom supervisor: ${message}`);
3340
3356
  return 1;
3341
3357
  };
@@ -3358,7 +3374,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3358
3374
  return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
3359
3375
  }
3360
3376
  const version = ports.headroomVersion() ?? "unknown";
3361
- writeHeadroomState(fs2, paths.headroomStateFile, { supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
3377
+ writeOwnState({ supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
3362
3378
  ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
3363
3379
  const orphanPid = previousState?.headroomPid;
3364
3380
  if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
@@ -3370,6 +3386,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3370
3386
  let runningSettingsHash;
3371
3387
  let consecutiveFailures = 0;
3372
3388
  let idleSince;
3389
+ let superseded = false;
3373
3390
  let ticks = 0;
3374
3391
  for (; ; ) {
3375
3392
  if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
@@ -3381,6 +3398,11 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3381
3398
  const allowlist = allowlistOf(ports);
3382
3399
  const allowlistHash = hashAllowlist(allowlist);
3383
3400
  pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
3401
+ const ownsNow = ownsState();
3402
+ if (!ownsNow && !superseded) {
3403
+ ports.log("claude-use headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
3404
+ }
3405
+ superseded = !ownsNow;
3384
3406
  const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
3385
3407
  if (headroomPid === void 0 || crashed) {
3386
3408
  if (crashed) {
@@ -3388,7 +3410,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3388
3410
  headroomPid = void 0;
3389
3411
  runningHash = void 0;
3390
3412
  runningSettingsHash = void 0;
3391
- writeHeadroomState(fs2, paths.headroomStateFile, {
3413
+ writeOwnState({
3392
3414
  supervisorPid: ports.ownPid,
3393
3415
  version,
3394
3416
  installedSource,
@@ -3416,7 +3438,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3416
3438
  `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)`
3417
3439
  );
3418
3440
  }
3419
- writeHeadroomState(fs2, paths.headroomStateFile, {
3441
+ const recorded = writeOwnState({
3420
3442
  supervisorPid: ports.ownPid,
3421
3443
  headroomPid: pid,
3422
3444
  port,
@@ -3426,7 +3448,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3426
3448
  installedSource: config.source,
3427
3449
  ...sticky()
3428
3450
  });
3429
- fs2.removeRecursive(paths.headroomLockFile);
3451
+ if (recorded) {
3452
+ fs2.removeRecursive(paths.headroomLockFile);
3453
+ }
3430
3454
  idleSince = void 0;
3431
3455
  ports.log(`claude-use headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
3432
3456
  } else {
@@ -3440,7 +3464,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3440
3464
  }
3441
3465
  } else {
3442
3466
  if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
3443
- if (listSessions(fs2, paths.headroomSessionsDir).length === 0) {
3467
+ if (sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid).length === 0) {
3444
3468
  ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
3445
3469
  ports.stopProcess(headroomPid);
3446
3470
  headroomPid = void 0;
@@ -3449,7 +3473,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3449
3473
  continue;
3450
3474
  }
3451
3475
  }
3452
- const sessions = listSessions(fs2, paths.headroomSessionsDir);
3476
+ const sessions = sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid);
3453
3477
  if (sessions.length === 0) {
3454
3478
  idleSince ??= ports.now();
3455
3479
  const idleMs = ports.now() - idleSince;
@@ -3458,7 +3482,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3458
3482
  `claude-use headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
3459
3483
  );
3460
3484
  ports.stopProcess(headroomPid);
3461
- writeHeadroomState(fs2, paths.headroomStateFile, {
3485
+ writeOwnState({
3462
3486
  version,
3463
3487
  ...sticky()
3464
3488
  });
package/dist/index.mjs CHANGED
@@ -2737,7 +2737,7 @@ var HeadroomStateSchema = z4.strictObject({
2737
2737
  /** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
2738
2738
  lastError: z4.string().optional()
2739
2739
  });
2740
- var HeadroomSessionSchema = z4.strictObject({ pid: z4.number().int().positive(), startedAt: z4.number() });
2740
+ var HeadroomSessionSchema = z4.strictObject({ pid: z4.number().int().positive(), startedAt: z4.number(), supervisorPid: z4.number().int().positive() });
2741
2741
  var HeadroomLockSchema = z4.strictObject({ pid: z4.number().int().positive(), at: z4.number() });
2742
2742
  function parseJson(raw, schema) {
2743
2743
  if (raw === void 0 || raw.trim() === "") {
@@ -2817,6 +2817,9 @@ function listSessions(fs2, sessionsDir) {
2817
2817
  }
2818
2818
  return sessions.sort((a, b) => a.pid - b.pid);
2819
2819
  }
2820
+ function sessionsForSupervisor(fs2, sessionsDir, supervisorPid) {
2821
+ return listSessions(fs2, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
2822
+ }
2820
2823
  function pruneDeadSessions(fs2, sessionsDir, isRunning) {
2821
2824
  const removed = [];
2822
2825
  for (const session of listSessions(fs2, sessionsDir)) {
@@ -3116,7 +3119,7 @@ function ensureHeadroom(params) {
3116
3119
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
3117
3120
  const daemonUp = state.port !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
3118
3121
  if (daemonUp && state.port !== void 0) {
3119
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now() });
3122
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
3120
3123
  return { port: state.port };
3121
3124
  }
3122
3125
  if (state.lastError !== void 0) {
@@ -3253,15 +3256,28 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3253
3256
  const { fs: fs2, paths } = ports;
3254
3257
  let config = initialConfig;
3255
3258
  const previousState = readHeadroomState(fs2, paths.headroomStateFile);
3259
+ if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
3260
+ ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
3261
+ return 0;
3262
+ }
3256
3263
  let installedSource = previousState?.installedSource;
3257
3264
  let lastPort = previousState?.lastPort;
3258
3265
  const sticky = () => lastPort === void 0 ? {} : { lastPort };
3266
+ const ownsState = () => {
3267
+ const owner = readHeadroomState(fs2, paths.headroomStateFile)?.supervisorPid;
3268
+ return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
3269
+ };
3270
+ const writeOwnState = (state) => {
3271
+ if (!ownsState()) {
3272
+ return false;
3273
+ }
3274
+ writeHeadroomState(fs2, paths.headroomStateFile, state);
3275
+ return true;
3276
+ };
3259
3277
  const fail = (message) => {
3260
- writeHeadroomState(fs2, paths.headroomStateFile, {
3261
- lastError: message,
3262
- ...sticky()
3263
- });
3264
- fs2.removeRecursive(paths.headroomLockFile);
3278
+ if (writeOwnState({ lastError: message, ...sticky() })) {
3279
+ fs2.removeRecursive(paths.headroomLockFile);
3280
+ }
3265
3281
  ports.log(`claude-use headroom supervisor: ${message}`);
3266
3282
  return 1;
3267
3283
  };
@@ -3284,7 +3300,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3284
3300
  return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
3285
3301
  }
3286
3302
  const version = ports.headroomVersion() ?? "unknown";
3287
- writeHeadroomState(fs2, paths.headroomStateFile, { supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
3303
+ writeOwnState({ supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
3288
3304
  ports.log(`claude-use headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
3289
3305
  const orphanPid = previousState?.headroomPid;
3290
3306
  if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
@@ -3296,6 +3312,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3296
3312
  let runningSettingsHash;
3297
3313
  let consecutiveFailures = 0;
3298
3314
  let idleSince;
3315
+ let superseded = false;
3299
3316
  let ticks = 0;
3300
3317
  for (; ; ) {
3301
3318
  if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
@@ -3307,6 +3324,11 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3307
3324
  const allowlist = allowlistOf(ports);
3308
3325
  const allowlistHash = hashAllowlist(allowlist);
3309
3326
  pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
3327
+ const ownsNow = ownsState();
3328
+ if (!ownsNow && !superseded) {
3329
+ ports.log("claude-use headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
3330
+ }
3331
+ superseded = !ownsNow;
3310
3332
  const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
3311
3333
  if (headroomPid === void 0 || crashed) {
3312
3334
  if (crashed) {
@@ -3314,7 +3336,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3314
3336
  headroomPid = void 0;
3315
3337
  runningHash = void 0;
3316
3338
  runningSettingsHash = void 0;
3317
- writeHeadroomState(fs2, paths.headroomStateFile, {
3339
+ writeOwnState({
3318
3340
  supervisorPid: ports.ownPid,
3319
3341
  version,
3320
3342
  installedSource,
@@ -3342,7 +3364,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3342
3364
  `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)`
3343
3365
  );
3344
3366
  }
3345
- writeHeadroomState(fs2, paths.headroomStateFile, {
3367
+ const recorded = writeOwnState({
3346
3368
  supervisorPid: ports.ownPid,
3347
3369
  headroomPid: pid,
3348
3370
  port,
@@ -3352,7 +3374,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3352
3374
  installedSource: config.source,
3353
3375
  ...sticky()
3354
3376
  });
3355
- fs2.removeRecursive(paths.headroomLockFile);
3377
+ if (recorded) {
3378
+ fs2.removeRecursive(paths.headroomLockFile);
3379
+ }
3356
3380
  idleSince = void 0;
3357
3381
  ports.log(`claude-use headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
3358
3382
  } else {
@@ -3366,7 +3390,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3366
3390
  }
3367
3391
  } else {
3368
3392
  if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
3369
- if (listSessions(fs2, paths.headroomSessionsDir).length === 0) {
3393
+ if (sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid).length === 0) {
3370
3394
  ports.log("claude-use headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
3371
3395
  ports.stopProcess(headroomPid);
3372
3396
  headroomPid = void 0;
@@ -3375,7 +3399,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3375
3399
  continue;
3376
3400
  }
3377
3401
  }
3378
- const sessions = listSessions(fs2, paths.headroomSessionsDir);
3402
+ const sessions = sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid);
3379
3403
  if (sessions.length === 0) {
3380
3404
  idleSince ??= ports.now();
3381
3405
  const idleMs = ports.now() - idleSince;
@@ -3384,7 +3408,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
3384
3408
  `claude-use headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
3385
3409
  );
3386
3410
  ports.stopProcess(headroomPid);
3387
- writeHeadroomState(fs2, paths.headroomStateFile, {
3411
+ writeOwnState({
3388
3412
  version,
3389
3413
  ...sticky()
3390
3414
  });
@@ -20,10 +20,13 @@ export declare const HeadroomStateSchema: z.ZodObject<{
20
20
  export type HeadroomState = z.infer<typeof HeadroomStateSchema>;
21
21
  /** The filesystem operations headroom coordination needs: the same shape as `FarmFs`, injected so every decision over state, sessions, and the start lock runs against an in-memory fake in tests. */
22
22
  export type HeadroomFs = Pick<FarmFs, "mkdirp" | "mkdirPrivate" | "readFileUtf8" | "writeFileUtf8" | "writeFilePrivate" | "writeFileExclusive" | "readdir" | "removeRecursive">;
23
- /** The record held in one session-registry file. */
23
+ /**
24
+ * The record held in one session-registry file. `supervisorPid` names the supervisor whose daemon the launch connected to: several supervisors can be alive at once (a superseded one keeps serving the sessions it already has), and each must judge idle shutdown and drift restarts by its own sessions only, or none would ever retire.
25
+ */
24
26
  declare const HeadroomSessionSchema: z.ZodObject<{
25
27
  pid: z.ZodNumber;
26
28
  startedAt: z.ZodNumber;
29
+ supervisorPid: z.ZodNumber;
27
30
  }, z.core.$strict>;
28
31
  export type HeadroomSession = z.infer<typeof HeadroomSessionSchema>;
29
32
  /** The exclusive-create start lock's recorded holder. */
@@ -63,6 +66,8 @@ export declare function writeSession(fs: HeadroomFs, sessionsDir: string, sessio
63
66
  export declare function removeSession(fs: HeadroomFs, sessionsDir: string, pid: number): void;
64
67
  /** Every registry entry on disk, unreadable files skipped, sorted by pid: what `headroom status` prints and what the supervisor's idle and drift decisions read. */
65
68
  export declare function listSessions(fs: HeadroomFs, sessionsDir: string): readonly HeadroomSession[];
69
+ /** The registry entries recorded against one supervisor: the sessions whose daemon that supervisor is responsible for keeping up. */
70
+ export declare function sessionsForSupervisor(fs: HeadroomFs, sessionsDir: string, supervisorPid: number): readonly HeadroomSession[];
66
71
  /**
67
72
  * Removes every registry entry whose pid is no longer running: a launcher that died without releasing its entry must not keep the daemon awake or block a drift restart forever. The predicate must be zombie-aware (see `realIsProcessRunning`): a launcher that exited but was never reaped still answers signal 0 as alive, which would keep its session registered indefinitely.
68
73
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-use",
3
- "version": "6.2.0",
3
+ "version": "6.2.1",
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>",