agent-shim 8.5.1 → 8.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.
package/dist/index.cjs CHANGED
@@ -2732,7 +2732,7 @@ function readFrontDoorState(fs12, stateFile) {
2732
2732
  }
2733
2733
  function writeFrontDoorState(fs12, stateFile, state) {
2734
2734
  fs12.mkdirp(import_node_path7.default.dirname(stateFile));
2735
- fs12.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
2735
+ fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
2736
2736
  `);
2737
2737
  }
2738
2738
  function frontDoorOrigin(state) {
@@ -3629,6 +3629,11 @@ function singleHeader(headers, name) {
3629
3629
  const value = headers[name];
3630
3630
  return typeof value === "string" ? value : void 0;
3631
3631
  }
3632
+ var OAUTH_AUTHORIZATION_PREFIX = "Bearer sk-ant-oat";
3633
+ function oauthAuthorizationOf(headers) {
3634
+ const value = singleHeader(headers, "authorization");
3635
+ return value?.startsWith(OAUTH_AUTHORIZATION_PREFIX) === true ? value : void 0;
3636
+ }
3632
3637
  function sessionCreateId(body) {
3633
3638
  let parsed;
3634
3639
  try {
@@ -3772,7 +3777,7 @@ function createRcSessionTracker(deps) {
3772
3777
  }
3773
3778
  const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
3774
3779
  const facts = {
3775
- authorization: singleHeader(request4.headers, "authorization"),
3780
+ oauthAuthorization: oauthAuthorizationOf(request4.headers),
3776
3781
  anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
3777
3782
  anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
3778
3783
  };
@@ -3814,7 +3819,7 @@ function createRcSessionTracker(deps) {
3814
3819
  }
3815
3820
  const entry = entries.get(id) ?? birth(id, facts);
3816
3821
  entry.lastSeenAt = deps.now();
3817
- entry.authorization = facts.authorization ?? entry.authorization;
3822
+ entry.oauthAuthorization = facts.oauthAuthorization ?? entry.oauthAuthorization;
3818
3823
  entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
3819
3824
  entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
3820
3825
  const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
@@ -3902,7 +3907,7 @@ function createRcSessionTracker(deps) {
3902
3907
  },
3903
3908
  credentialOf: (sessionId) => {
3904
3909
  const entry = entries.get(sessionId);
3905
- return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3910
+ return entry === void 0 ? void 0 : { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3906
3911
  },
3907
3912
  completePending: (sessionId, requestId) => {
3908
3913
  entries.get(sessionId)?.pending.delete(requestId);
@@ -4025,6 +4030,9 @@ function rcEventWriteResultFromAnswer(answer) {
4025
4030
  }
4026
4031
  return { ok: true, sequenceNums };
4027
4032
  }
4033
+ function noOAuthCredentialResult(sessionId) {
4034
+ return { ok: false, message: `no claude.ai OAuth bearer has been observed for session ${sessionId}: only worker calls crossed this door, and the worker JWT they carry is answered 401 on the client half. Reconnect Remote Control through this door and the create's own bearer will be observed` };
4035
+ }
4028
4036
  function rcWriteHeaders(credential) {
4029
4037
  const headers = { "content-type": "application/json" };
4030
4038
  if (credential.authorization !== void 0) {
@@ -4043,6 +4051,9 @@ async function injectRcUserMessage(deps, sessionId, text) {
4043
4051
  if (credential === void 0) {
4044
4052
  return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
4045
4053
  }
4054
+ if (credential.authorization === void 0) {
4055
+ return noOAuthCredentialResult(sessionId);
4056
+ }
4046
4057
  const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
4047
4058
  let answer;
4048
4059
  try {
@@ -4061,6 +4072,9 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
4061
4072
  if (credential === void 0) {
4062
4073
  return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
4063
4074
  }
4075
+ if (credential.authorization === void 0) {
4076
+ return noOAuthCredentialResult(sessionId);
4077
+ }
4064
4078
  if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
4065
4079
  return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
4066
4080
  }
@@ -4937,13 +4951,24 @@ function ensureHeadroom(params) {
4937
4951
  const { paths, ports } = params;
4938
4952
  const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
4939
4953
  let spawnedSupervisor = false;
4954
+ let awaitingAllowlist = false;
4940
4955
  for (; ; ) {
4941
4956
  const state = readHeadroomState(ports.fs, paths.headroomStateFile);
4942
4957
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
4943
4958
  const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
4944
4959
  if (daemonUp && state.socketPath !== void 0) {
4945
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
4946
- return { socketPath: state.socketPath };
4960
+ const required = params.requiredAllowlistHash?.();
4961
+ if (required === void 0 || state.allowlistHash === required) {
4962
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
4963
+ return { socketPath: state.socketPath };
4964
+ }
4965
+ const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
4966
+ if (holders.length > 0) {
4967
+ throw new HeadroomStartError(
4968
+ `agent-shim: the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
4969
+ );
4970
+ }
4971
+ awaitingAllowlist = true;
4947
4972
  }
4948
4973
  if (state.lastError !== void 0) {
4949
4974
  throw new HeadroomStartError(
@@ -4971,7 +4996,7 @@ function ensureHeadroom(params) {
4971
4996
  if (ports.now() >= deadline) {
4972
4997
  const lastError = state?.lastError;
4973
4998
  throw new HeadroomStartError(
4974
- `agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + `. Daemon log: ${paths.headroomLogPath}`
4999
+ `agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
4975
5000
  );
4976
5001
  }
4977
5002
  ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
@@ -6987,18 +7012,14 @@ function discoverClaudeBinary(options) {
6987
7012
  version: highestVersion
6988
7013
  };
6989
7014
  }
6990
- const ownInstallDirs = new Set(options.ownInstallDirs.map((dir) => import_node_path20.default.resolve(dir)));
6991
7015
  for (const dir of options.pathDirs) {
6992
- if (ownInstallDirs.has(import_node_path20.default.resolve(dir))) {
6993
- continue;
6994
- }
6995
7016
  const found = options.findExecutableInDir(dir, "claude");
6996
- if (found !== void 0) {
7017
+ if (found !== void 0 && !options.isOwnBinary(found)) {
6997
7018
  return { path: found, source: "path-fallback" };
6998
7019
  }
6999
7020
  }
7000
7021
  throw new Error(
7001
- `Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (excluding this tool's own install directory). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
7022
+ `Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (never this tool's own binary). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
7002
7023
  );
7003
7024
  }
7004
7025
 
@@ -7274,10 +7295,11 @@ var realHeadroomSocketTrust = {
7274
7295
  };
7275
7296
  function realHeadroomPort(paths, options) {
7276
7297
  return {
7277
- ensure: () => {
7298
+ ensure: ({ routesProvider }) => {
7278
7299
  const up = ensureHeadroom({
7279
7300
  paths,
7280
7301
  launcherPid: process.pid,
7302
+ ...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
7281
7303
  ports: {
7282
7304
  fs: realFarmFs,
7283
7305
  // 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.
@@ -7396,12 +7418,18 @@ function realOwnExecutablePath() {
7396
7418
  findExecutableInDir
7397
7419
  });
7398
7420
  }
7399
- function realResolveClaudeBinary(ownInstallDirs) {
7421
+ function resolveContentSourcePath(env) {
7422
+ return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
7423
+ }
7424
+ function realContentSourcePath() {
7425
+ return resolveContentSourcePath({ isSea: (0, import_node_sea.isSea)(), execPath: process.execPath, argv1: process.argv[1] });
7426
+ }
7427
+ function realResolveClaudeBinary(isOwnBinary) {
7400
7428
  return () => discoverClaudeBinary({
7401
7429
  listVersionsDir,
7402
7430
  pathDirs: (process.env.PATH ?? "").split(import_node_path21.default.delimiter).filter((dir) => dir !== ""),
7403
7431
  findExecutableInDir,
7404
- ownInstallDirs: [...ownInstallDirs]
7432
+ isOwnBinary
7405
7433
  });
7406
7434
  }
7407
7435
 
@@ -7817,13 +7845,32 @@ var ClaudeShimStateSchema = import_zod11.z.strictObject({
7817
7845
  function commandFilename(ownExecutablePath, commandName) {
7818
7846
  return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
7819
7847
  }
7820
- function resolveOwnInstallDirs(paths, ownExecutablePath) {
7821
- const dirs = [import_node_path23.default.dirname(ownExecutablePath)];
7848
+ function resolveOwnBinaryCheck(paths, contentSourcePath) {
7822
7849
  const state = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
7823
- if (state !== void 0) {
7824
- dirs.push(import_node_path23.default.dirname(state.targetPath));
7850
+ const recordedTarget = state === void 0 ? void 0 : import_node_path23.default.resolve(state.targetPath);
7851
+ let ownStat;
7852
+ return (candidate) => {
7853
+ if (recordedTarget !== void 0 && import_node_path23.default.resolve(candidate) === recordedTarget) {
7854
+ return true;
7855
+ }
7856
+ ownStat ??= statOrUndefined(contentSourcePath);
7857
+ const candidateStat = statOrUndefined(candidate);
7858
+ return ownStat !== void 0 && candidateStat !== void 0 && // A platform reporting no inode number for either file must not read every candidate as this tool.
7859
+ ownStat.ino !== 0 && candidateStat.ino !== 0 && candidateStat.dev === ownStat.dev && candidateStat.ino === ownStat.ino;
7860
+ };
7861
+ }
7862
+ function isEnoent4(error) {
7863
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
7864
+ }
7865
+ function statOrUndefined(target) {
7866
+ try {
7867
+ return import_node_fs7.default.statSync(target);
7868
+ } catch (error) {
7869
+ if (isEnoent4(error)) {
7870
+ return void 0;
7871
+ }
7872
+ throw error;
7825
7873
  }
7826
- return dirs;
7827
7874
  }
7828
7875
  var nodeLinkFs = { link: import_node_fs7.default.linkSync, copyFile: import_node_fs7.default.copyFileSync, chmod: import_node_fs7.default.chmodSync };
7829
7876
  function findPathShadow(params) {
@@ -8264,7 +8311,7 @@ function collectDoctorReport(params) {
8264
8311
  const ownExecutablePath = realOwnExecutablePath();
8265
8312
  let binaryDiscovery;
8266
8313
  try {
8267
- const binary = realResolveClaudeBinary(resolveOwnInstallDirs(paths, ownExecutablePath))();
8314
+ const binary = realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath()))();
8268
8315
  binaryDiscovery = { ok: true, binary };
8269
8316
  } catch (error) {
8270
8317
  binaryDiscovery = { ok: false, message: error instanceof Error ? error.message : String(error) };
@@ -8936,7 +8983,7 @@ function prepareLaunch(params) {
8936
8983
  let headroom;
8937
8984
  const headroomPort = params.headroom;
8938
8985
  if (resolvedFlags.headroom && headroomPort !== void 0) {
8939
- headroom = headroomPort.ensure();
8986
+ headroom = headroomPort.ensure({ routesProvider: resolvedProvider !== void 0 });
8940
8987
  const via = resolvedProvider === void 0 ? `OAuth via the door's CONNECT surface on 127.0.0.1:${String(frontDoor?.connectPort ?? 0)}` : `provider ${resolvedProvider.name}`;
8941
8988
  log.info(`agent-shim: routing through the front door on 127.0.0.1:${String(frontDoor?.port ?? 0)} with headroom on unix socket ${headroom.socketPath} (${via}, project ${headroom.projectId})`);
8942
8989
  } else if (resolvedFlags.headroom) {
@@ -9240,7 +9287,7 @@ function realPrepareLaunchParams(paths, options) {
9240
9287
  fs: realFsPort,
9241
9288
  proc: options.proc,
9242
9289
  log: options.log,
9243
- resolveClaudeBinary: realResolveClaudeBinary(resolveOwnInstallDirs(paths, realOwnExecutablePath())),
9290
+ resolveClaudeBinary: realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath())),
9244
9291
  farm: farm.runtime,
9245
9292
  headroom: realHeadroomPort(paths, { spawnDaemon: options.spawnDaemon, cwd: farm.runtime.cwd }),
9246
9293
  frontdoor: realFrontDoorPort(paths, options.spawnDaemon),
package/dist/index.mjs CHANGED
@@ -2548,7 +2548,7 @@ function readFrontDoorState(fs12, stateFile) {
2548
2548
  }
2549
2549
  function writeFrontDoorState(fs12, stateFile, state) {
2550
2550
  fs12.mkdirp(path7.dirname(stateFile));
2551
- fs12.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
2551
+ fs12.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
2552
2552
  `);
2553
2553
  }
2554
2554
  function frontDoorOrigin(state) {
@@ -3445,6 +3445,11 @@ function singleHeader(headers, name) {
3445
3445
  const value = headers[name];
3446
3446
  return typeof value === "string" ? value : void 0;
3447
3447
  }
3448
+ var OAUTH_AUTHORIZATION_PREFIX = "Bearer sk-ant-oat";
3449
+ function oauthAuthorizationOf(headers) {
3450
+ const value = singleHeader(headers, "authorization");
3451
+ return value?.startsWith(OAUTH_AUTHORIZATION_PREFIX) === true ? value : void 0;
3452
+ }
3448
3453
  function sessionCreateId(body) {
3449
3454
  let parsed;
3450
3455
  try {
@@ -3588,7 +3593,7 @@ function createRcSessionTracker(deps) {
3588
3593
  }
3589
3594
  const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
3590
3595
  const facts = {
3591
- authorization: singleHeader(request4.headers, "authorization"),
3596
+ oauthAuthorization: oauthAuthorizationOf(request4.headers),
3592
3597
  anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
3593
3598
  anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
3594
3599
  };
@@ -3630,7 +3635,7 @@ function createRcSessionTracker(deps) {
3630
3635
  }
3631
3636
  const entry = entries.get(id) ?? birth(id, facts);
3632
3637
  entry.lastSeenAt = deps.now();
3633
- entry.authorization = facts.authorization ?? entry.authorization;
3638
+ entry.oauthAuthorization = facts.oauthAuthorization ?? entry.oauthAuthorization;
3634
3639
  entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
3635
3640
  entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
3636
3641
  const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
@@ -3718,7 +3723,7 @@ function createRcSessionTracker(deps) {
3718
3723
  },
3719
3724
  credentialOf: (sessionId) => {
3720
3725
  const entry = entries.get(sessionId);
3721
- return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3726
+ return entry === void 0 ? void 0 : { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
3722
3727
  },
3723
3728
  completePending: (sessionId, requestId) => {
3724
3729
  entries.get(sessionId)?.pending.delete(requestId);
@@ -3841,6 +3846,9 @@ function rcEventWriteResultFromAnswer(answer) {
3841
3846
  }
3842
3847
  return { ok: true, sequenceNums };
3843
3848
  }
3849
+ function noOAuthCredentialResult(sessionId) {
3850
+ return { ok: false, message: `no claude.ai OAuth bearer has been observed for session ${sessionId}: only worker calls crossed this door, and the worker JWT they carry is answered 401 on the client half. Reconnect Remote Control through this door and the create's own bearer will be observed` };
3851
+ }
3844
3852
  function rcWriteHeaders(credential) {
3845
3853
  const headers = { "content-type": "application/json" };
3846
3854
  if (credential.authorization !== void 0) {
@@ -3859,6 +3867,9 @@ async function injectRcUserMessage(deps, sessionId, text) {
3859
3867
  if (credential === void 0) {
3860
3868
  return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
3861
3869
  }
3870
+ if (credential.authorization === void 0) {
3871
+ return noOAuthCredentialResult(sessionId);
3872
+ }
3862
3873
  const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
3863
3874
  let answer;
3864
3875
  try {
@@ -3877,6 +3888,9 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
3877
3888
  if (credential === void 0) {
3878
3889
  return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
3879
3890
  }
3891
+ if (credential.authorization === void 0) {
3892
+ return noOAuthCredentialResult(sessionId);
3893
+ }
3880
3894
  if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
3881
3895
  return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
3882
3896
  }
@@ -4753,13 +4767,24 @@ function ensureHeadroom(params) {
4753
4767
  const { paths, ports } = params;
4754
4768
  const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
4755
4769
  let spawnedSupervisor = false;
4770
+ let awaitingAllowlist = false;
4756
4771
  for (; ; ) {
4757
4772
  const state = readHeadroomState(ports.fs, paths.headroomStateFile);
4758
4773
  if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
4759
4774
  const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
4760
4775
  if (daemonUp && state.socketPath !== void 0) {
4761
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
4762
- return { socketPath: state.socketPath };
4776
+ const required = params.requiredAllowlistHash?.();
4777
+ if (required === void 0 || state.allowlistHash === required) {
4778
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
4779
+ return { socketPath: state.socketPath };
4780
+ }
4781
+ const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
4782
+ if (holders.length > 0) {
4783
+ throw new HeadroomStartError(
4784
+ `agent-shim: the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
4785
+ );
4786
+ }
4787
+ awaitingAllowlist = true;
4763
4788
  }
4764
4789
  if (state.lastError !== void 0) {
4765
4790
  throw new HeadroomStartError(
@@ -4787,7 +4812,7 @@ function ensureHeadroom(params) {
4787
4812
  if (ports.now() >= deadline) {
4788
4813
  const lastError = state?.lastError;
4789
4814
  throw new HeadroomStartError(
4790
- `agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + `. Daemon log: ${paths.headroomLogPath}`
4815
+ `agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
4791
4816
  );
4792
4817
  }
4793
4818
  ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
@@ -6803,18 +6828,14 @@ function discoverClaudeBinary(options) {
6803
6828
  version: highestVersion
6804
6829
  };
6805
6830
  }
6806
- const ownInstallDirs = new Set(options.ownInstallDirs.map((dir) => path20.resolve(dir)));
6807
6831
  for (const dir of options.pathDirs) {
6808
- if (ownInstallDirs.has(path20.resolve(dir))) {
6809
- continue;
6810
- }
6811
6832
  const found = options.findExecutableInDir(dir, "claude");
6812
- if (found !== void 0) {
6833
+ if (found !== void 0 && !options.isOwnBinary(found)) {
6813
6834
  return { path: found, source: "path-fallback" };
6814
6835
  }
6815
6836
  }
6816
6837
  throw new Error(
6817
- `Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (excluding this tool's own install directory). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
6838
+ `Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (never this tool's own binary). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
6818
6839
  );
6819
6840
  }
6820
6841
 
@@ -7090,10 +7111,11 @@ var realHeadroomSocketTrust = {
7090
7111
  };
7091
7112
  function realHeadroomPort(paths, options) {
7092
7113
  return {
7093
- ensure: () => {
7114
+ ensure: ({ routesProvider }) => {
7094
7115
  const up = ensureHeadroom({
7095
7116
  paths,
7096
7117
  launcherPid: process.pid,
7118
+ ...routesProvider ? { requiredAllowlistHash: () => hashAllowlist(currentHeadroomAllowlist(realFarmFs, paths)) } : {},
7097
7119
  ports: {
7098
7120
  fs: realFarmFs,
7099
7121
  // 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.
@@ -7212,12 +7234,18 @@ function realOwnExecutablePath() {
7212
7234
  findExecutableInDir
7213
7235
  });
7214
7236
  }
7215
- function realResolveClaudeBinary(ownInstallDirs) {
7237
+ function resolveContentSourcePath(env) {
7238
+ return env.isSea ? env.execPath : env.argv1 ?? env.execPath;
7239
+ }
7240
+ function realContentSourcePath() {
7241
+ return resolveContentSourcePath({ isSea: isSea(), execPath: process.execPath, argv1: process.argv[1] });
7242
+ }
7243
+ function realResolveClaudeBinary(isOwnBinary) {
7216
7244
  return () => discoverClaudeBinary({
7217
7245
  listVersionsDir,
7218
7246
  pathDirs: (process.env.PATH ?? "").split(path21.delimiter).filter((dir) => dir !== ""),
7219
7247
  findExecutableInDir,
7220
- ownInstallDirs: [...ownInstallDirs]
7248
+ isOwnBinary
7221
7249
  });
7222
7250
  }
7223
7251
 
@@ -7633,13 +7661,32 @@ var ClaudeShimStateSchema = z11.strictObject({
7633
7661
  function commandFilename(ownExecutablePath, commandName) {
7634
7662
  return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
7635
7663
  }
7636
- function resolveOwnInstallDirs(paths, ownExecutablePath) {
7637
- const dirs = [path23.dirname(ownExecutablePath)];
7664
+ function resolveOwnBinaryCheck(paths, contentSourcePath) {
7638
7665
  const state = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
7639
- if (state !== void 0) {
7640
- dirs.push(path23.dirname(state.targetPath));
7666
+ const recordedTarget = state === void 0 ? void 0 : path23.resolve(state.targetPath);
7667
+ let ownStat;
7668
+ return (candidate) => {
7669
+ if (recordedTarget !== void 0 && path23.resolve(candidate) === recordedTarget) {
7670
+ return true;
7671
+ }
7672
+ ownStat ??= statOrUndefined(contentSourcePath);
7673
+ const candidateStat = statOrUndefined(candidate);
7674
+ return ownStat !== void 0 && candidateStat !== void 0 && // A platform reporting no inode number for either file must not read every candidate as this tool.
7675
+ ownStat.ino !== 0 && candidateStat.ino !== 0 && candidateStat.dev === ownStat.dev && candidateStat.ino === ownStat.ino;
7676
+ };
7677
+ }
7678
+ function isEnoent4(error) {
7679
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
7680
+ }
7681
+ function statOrUndefined(target) {
7682
+ try {
7683
+ return fs8.statSync(target);
7684
+ } catch (error) {
7685
+ if (isEnoent4(error)) {
7686
+ return void 0;
7687
+ }
7688
+ throw error;
7641
7689
  }
7642
- return dirs;
7643
7690
  }
7644
7691
  var nodeLinkFs = { link: fs8.linkSync, copyFile: fs8.copyFileSync, chmod: fs8.chmodSync };
7645
7692
  function findPathShadow(params) {
@@ -8080,7 +8127,7 @@ function collectDoctorReport(params) {
8080
8127
  const ownExecutablePath = realOwnExecutablePath();
8081
8128
  let binaryDiscovery;
8082
8129
  try {
8083
- const binary = realResolveClaudeBinary(resolveOwnInstallDirs(paths, ownExecutablePath))();
8130
+ const binary = realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath()))();
8084
8131
  binaryDiscovery = { ok: true, binary };
8085
8132
  } catch (error) {
8086
8133
  binaryDiscovery = { ok: false, message: error instanceof Error ? error.message : String(error) };
@@ -8752,7 +8799,7 @@ function prepareLaunch(params) {
8752
8799
  let headroom;
8753
8800
  const headroomPort = params.headroom;
8754
8801
  if (resolvedFlags.headroom && headroomPort !== void 0) {
8755
- headroom = headroomPort.ensure();
8802
+ headroom = headroomPort.ensure({ routesProvider: resolvedProvider !== void 0 });
8756
8803
  const via = resolvedProvider === void 0 ? `OAuth via the door's CONNECT surface on 127.0.0.1:${String(frontDoor?.connectPort ?? 0)}` : `provider ${resolvedProvider.name}`;
8757
8804
  log.info(`agent-shim: routing through the front door on 127.0.0.1:${String(frontDoor?.port ?? 0)} with headroom on unix socket ${headroom.socketPath} (${via}, project ${headroom.projectId})`);
8758
8805
  } else if (resolvedFlags.headroom) {
@@ -9056,7 +9103,7 @@ function realPrepareLaunchParams(paths, options) {
9056
9103
  fs: realFsPort,
9057
9104
  proc: options.proc,
9058
9105
  log: options.log,
9059
- resolveClaudeBinary: realResolveClaudeBinary(resolveOwnInstallDirs(paths, realOwnExecutablePath())),
9106
+ resolveClaudeBinary: realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath())),
9060
9107
  farm: farm.runtime,
9061
9108
  headroom: realHeadroomPort(paths, { spawnDaemon: options.spawnDaemon, cwd: farm.runtime.cwd }),
9062
9109
  frontdoor: realFrontDoorPort(paths, options.spawnDaemon),
@@ -1171,10 +1171,12 @@ interface HeadroomUp {
1171
1171
  /**
1172
1172
  * Everything the launcher needs from the headroom daemon, injected so its daemon-supervision side effects stay behind a port and the launcher's own logic stays pure over it (`src/realPorts.ts` wires the real one; tests wire fakes).
1173
1173
  *
1174
- * `ensure` brings the daemon up (spawning the detached supervisor when nothing healthy is running) and registers this launch in its session registry; `release` removes that registration when the spawned `claude` exits.
1174
+ * `ensure` brings the daemon up (spawning the detached supervisor when nothing healthy is running) and registers this launch in its session registry; `release` removes that registration when the spawned `claude` exits. `routesProvider` says the launch sends a provider's traffic through the daemon, which needs the daemon to have been started with the front door's current address on its allowlist: `ensure` then waits for a restart that brings it, or refuses when live sessions hold the stale daemon up, instead of registering against a daemon that would reject every request.
1175
1175
  */
1176
1176
  interface HeadroomPort {
1177
- readonly ensure: () => HeadroomUp;
1177
+ readonly ensure: (options: Readonly<{
1178
+ routesProvider: boolean;
1179
+ }>) => HeadroomUp;
1178
1180
  readonly release: () => void;
1179
1181
  }
1180
1182
  /**
@@ -1767,7 +1769,7 @@ declare function rcObserverAsPassthrough(observer: RcExchangeObserver): Passthro
1767
1769
  declare function buildRcUserMessagePayload(uuid: string, sessionId: string, text: string): Record<string, unknown>;
1768
1770
  /** Wraps payload event(s) in the write body the endpoint takes: `{"events": [{"payload": {...}}]}`. */
1769
1771
  declare function buildRcEventWriteBody(payload: Record<string, unknown>): Record<string, unknown>;
1770
- /** The headers a client-half write sends: the observed credential and protocol values, replayed verbatim, never invented. */
1772
+ /** The headers a client-half write sends: the observed OAuth-kind credential and protocol values, replayed verbatim, never invented. */
1771
1773
  interface RcWriteHeaders {
1772
1774
  readonly authorization: string | undefined;
1773
1775
  readonly anthropicVersion: string | undefined;
@@ -2115,6 +2117,10 @@ declare function ensureHeadroom(params: {
2115
2117
  readonly paths: LayoutPaths;
2116
2118
  readonly launcherPid: number;
2117
2119
  readonly ports: EnsureHeadroomPorts;
2120
+ /**
2121
+ * The hash of the allowlist the daemon must have been started with for this launch to work, read fresh on every poll because the front door's address it contains can change while the launch waits. Given for a launch that routes a provider through headroom, whose requests come back to the front door's direct origin and are refused by a daemon that was started without it; absent for a launch that only needs Claude Code's own API, which every daemon admits.
2122
+ */
2123
+ readonly requiredAllowlistHash?: () => string;
2118
2124
  }): {
2119
2125
  readonly socketPath: string;
2120
2126
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.5.1",
3
+ "version": "8.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": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",