@integrity-labs/agt-cli 0.28.1157 → 0.28.1158

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/bin/agt.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  success,
41
41
  table,
42
42
  warn
43
- } from "../chunk-JLNUGWCP.js";
43
+ } from "../chunk-VYBTCFQQ.js";
44
44
  import {
45
45
  getProjectDir,
46
46
  isSessionResumeDisabled,
@@ -5513,7 +5513,7 @@ import { execFileSync, execSync } from "child_process";
5513
5513
  import { existsSync as existsSync11, realpathSync as realpathSync2 } from "fs";
5514
5514
  import chalk18 from "chalk";
5515
5515
  import ora16 from "ora";
5516
- var cliVersion = true ? "0.28.1157" : "dev";
5516
+ var cliVersion = true ? "0.28.1158" : "dev";
5517
5517
  async function fetchLatestVersion() {
5518
5518
  const host2 = getHost();
5519
5519
  if (!host2) return null;
@@ -6773,7 +6773,7 @@ function handleError(err) {
6773
6773
  }
6774
6774
 
6775
6775
  // src/bin/agt.ts
6776
- var cliVersion2 = true ? "0.28.1157" : "dev";
6776
+ var cliVersion2 = true ? "0.28.1158" : "dev";
6777
6777
  var program = new Command();
6778
6778
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
6779
6779
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -21,9 +21,8 @@ import { execFileSync } from "child_process";
21
21
  var GIB = 1024 * 1024 * 1024;
22
22
  var DISK_LOW_FREE_RATIO = 0.1;
23
23
  var DISK_LOW_FREE_BYTES = 2 * GIB;
24
- var DISK_LOW_FREE_CAP_BYTES = 5 * GIB;
25
24
  function diskLowThresholdBytes(totalBytes) {
26
- return Math.min(DISK_LOW_FREE_CAP_BYTES, Math.max(DISK_LOW_FREE_BYTES, totalBytes * DISK_LOW_FREE_RATIO));
25
+ return Math.max(DISK_LOW_FREE_BYTES, totalBytes * DISK_LOW_FREE_RATIO);
27
26
  }
28
27
  var SELF_UPDATE_MIN_FREE_BYTES = 1 * GIB;
29
28
  var DISK_GUARD_INTERVAL_MS = 60 * 60 * 1e3;
@@ -715,4 +714,4 @@ export {
715
714
  probeClaudeInstalls,
716
715
  maybeUpdateClaudeCode
717
716
  };
718
- //# sourceMappingURL=chunk-7NAL24SM.js.map
717
+ //# sourceMappingURL=chunk-3DISC7BU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/claude-code-updater.ts","../src/lib/disk-guard.ts"],"sourcesContent":["// ENG-5672: idle-gated, server-pinned Claude Code auto-updater.\n// ENG-5798: multi-install drift handling — detect coexisting claude binaries,\n// short-circuit on native autoupdater hosts, verify against absolute\n// install path, hard-error after N consecutive verify mismatches.\n//\n// Hosts install @anthropic-ai/claude-code once at bootstrap (host-bootstrap.ts)\n// and never refresh it, so a long-lived host runs a stale Claude Code forever.\n// This module brings a host to its *desired* version, which the server pins via\n// /host/refresh (per-host override, else fleet default, else 'latest').\n//\n// Safety properties (this runs npm install -g on live prod hosts):\n// • Idle-gated — we never install while a `claude --name agt-*` agent process\n// is running on the host (a running process holds the old binary anyway;\n// replacing it under a live session is pointless and risky). Busy → defer\n// to a later cycle.\n// • No-op when already on target — a concrete target equal to the installed\n// version short-circuits; a dist-tag ('latest') is resolved to a concrete\n// version via `npm view` and compared, so we don't reinstall every cycle.\n// • Host-global throttle — at most one check per interval (default 6h), so the\n// manager's ~10-30s poll doesn't shell out to npm/ps constantly.\n// • Log-and-continue — every failure is logged and swallowed; an error here\n// must never break the manager poll cycle.\n// • Multi-install awareness (ENG-5798) — on hosts where Anthropic's native\n// autoupdater owns /usr/bin/claude, this updater short-circuits cleanly\n// instead of fighting it. On hosts where multiple binaries coexist (e.g. a\n// brew cask alongside the npm global), we log every path+version once so\n// drift is visible in manager.log without spamming every cycle.\n//\n// The *installed* version is reported separately as hosts.framework_version\n// (the claude-code framework adapter's getVersion() == `claude --version`), so\n// this module does not report it itself.\n\nimport { execFile, execFileSync } from 'node:child_process';\nimport { existsSync, readFileSync, realpathSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { promisify } from 'node:util';\nimport { effectiveClaudeJsonPath } from './claude-config-location.js';\nimport { parsePsRows, CLAUDE_AGENT_PROCESS_PATTERN } from './orphan-channel-mcp-reaper.js';\nimport { FORCED_UPDATE_HARD_DEADLINE_MS } from '@augmented/core';\nimport { withPackageCacheLock } from './disk-guard.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst NPM_PKG = '@anthropic-ai/claude-code';\n\n/**\n * ENG-11641 follow-up: the minimum host Claude Code version, for security.\n *\n * The npm package is a native binary with its own bundled Bun, so patching\n * system Node does nothing for it. Versions before 2.1.308 bundle a Bun whose\n * Brotli decoder predates the CVE-2026-111497 (Node2Shell) fix, and 2.1.280 /\n * 2.1.296 also ship the compromised emSign ECC Root CA - G3 in their built-in\n * trust store (eMudhra intermediate key compromise, oss-security 2026-10-20).\n * #7108 fixed the agt-runtime IMAGE; it did nothing for the host's own global\n * install, which #7108 measured at 2.1.280 on 22 of 24 prod hosts. Those hosts\n * never converge because this updater only runs inside the maintenance window\n * AND only when no agent is live, and a working fleet is never idle.\n *\n * A host below this floor therefore:\n * • has its target raised to the floor when the pin/fleet default is lower\n * (a pin below a security floor IS the vulnerability, so it is overridden\n * and logged, not honoured);\n * • bypasses the maintenance window (see manager-worker's call site);\n * • still defers while an agent is live, for at most\n * FORCED_UPDATE_HARD_DEADLINE_MS (the same deadline the \"Update CLI now\"\n * forced update uses, ENG-8449), then installs under live agents ONLY IF\n * the npm install slot is provably the binary agents run (its version is\n * readable, below the floor and equal to PATH's). Otherwise — unresolvable\n * slot, unreadable slot, slot already fixed, or slot on a different version\n * (the ENG-5798 multi-install case) — it does NOT force, logs\n * `ERROR security_floor_blocked`, and the host stays below the floor until\n * an operator fixes its install slots. That is a known, alarmed limit, not\n * a guarantee of convergence.\n *\n * Raise this when a new security floor is set; never lower it.\n */\nexport const CLAUDE_CODE_SECURITY_FLOOR = '2.1.308';\n\n/** How soon to re-check a below-floor host that deferred because it was busy. */\nconst BELOW_FLOOR_RECHECK_MS = 60_000;\n\n/** Default host-global check interval. Overridable via env for tuning. */\nconst DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6h\n\n/**\n * ENG-5798: After this many consecutive verify mismatches against the same\n * target version, emit a distinct one-shot error code that the\n * synthetic-probe / CloudWatch alarm pipeline can latch onto, instead of\n * silently re-failing forever. Overridable via env for testing.\n */\nconst DEFAULT_VERIFY_MISMATCH_THRESHOLD = 5;\n\nfunction intervalMs(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_INTERVAL_MS'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INTERVAL_MS;\n}\n\nfunction verifyMismatchThreshold(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_VERIFY_THRESHOLD'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_VERIFY_MISMATCH_THRESHOLD;\n}\n\n/** Extract a version string (leading semver) from `claude --version` / npm output. */\nexport function parseVersion(output: string): string | null {\n const m = output.match(/\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*/);\n return m ? m[0] : null;\n}\n\n/** A target spec is \"concrete\" when it already names an exact version. */\nexport function isConcreteVersion(spec: string): boolean {\n return /^\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*$/.test(spec.trim());\n}\n\n/**\n * Compare two concrete versions by their numeric major.minor.patch core.\n * A prerelease (`1.2.3-beta.1`) sorts BEFORE its release (`1.2.3`), as in\n * semver. Returns null when either side does not parse, so callers can fail\n * closed rather than guess.\n */\nexport function compareVersions(a: string, b: string): number | null {\n const re = /^(\\d+)\\.(\\d+)\\.(\\d+)(-[0-9A-Za-z.-]+)?/;\n const ma = re.exec(a.trim());\n const mb = re.exec(b.trim());\n if (!ma || !mb) return null;\n for (let i = 1; i <= 3; i++) {\n const d = Number(ma[i]) - Number(mb[i]);\n if (d !== 0) return d < 0 ? -1 : 1;\n }\n const preA = ma[4] !== undefined;\n const preB = mb[4] !== undefined;\n if (preA === preB) return 0;\n return preA ? -1 : 1;\n}\n\n/**\n * Whether the manager should call the updater on this poll (ENG-11641\n * follow-up). Normally only inside the maintenance window; a host whose\n * installed Claude Code is below the security floor runs it regardless of the\n * window. No server target means \"change nothing\" (ENG-10620).\n */\nexport function shouldRunClaudeCodeUpdater(input: {\n desiredVersion: string | null | undefined;\n installedVersion: string | null | undefined;\n windowGate: 'proceed' | 'defer';\n floor?: string;\n}): boolean {\n if (!input.desiredVersion) return false;\n if (isBelowSecurityFloor(input.installedVersion, input.floor)) return true;\n return input.windowGate === 'proceed';\n}\n\n/**\n * True when `version` is a known version strictly below\n * {@link CLAUDE_CODE_SECURITY_FLOOR}. Unknown/unparseable → false: we only\n * escalate past the normal gates on a measured version, never on a guess.\n */\nexport function isBelowSecurityFloor(\n version: string | null | undefined,\n floor: string = CLAUDE_CODE_SECURITY_FLOOR,\n): boolean {\n if (!version) return false;\n const parsed = parseVersion(version);\n if (!parsed) return false;\n const cmp = compareVersions(parsed, floor);\n return cmp !== null && cmp < 0;\n}\n\n/**\n * Well-known absolute paths where a `claude` binary may live on managed hosts.\n * Probed alongside the PATH-resolved binary so we can see all install slots,\n * even ones not on the manager systemd unit's PATH.\n */\nexport const WELL_KNOWN_CLAUDE_PATHS: readonly string[] = [\n '/usr/bin/claude', // Anthropic native autoupdater (Linux deb/rpm)\n '/usr/local/bin/claude', // npm global on macOS Intel + Linux without nvm\n '/opt/homebrew/bin/claude', // Homebrew on macOS Apple Silicon\n '/home/linuxbrew/.linuxbrew/bin/claude', // Linuxbrew (used on agt-aws-* hosts)\n];\n\nexport interface ClaudeCodeUpdaterDeps {\n /** Server-pinned target: a concrete version or an npm dist-tag like 'latest'. */\n desiredVersion: string;\n /** Currently-installed version, if the caller already knows it (e.g. the\n * manager's cachedFrameworkVersion). When omitted we run `claude --version`. */\n currentVersion?: string | null;\n log: (msg: string) => void;\n /** Override \"now\" for tests. */\n now?: () => number;\n /** Run a command; injected in tests. Defaults to execFile. */\n runCommand?: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>;\n /** Raw `ps -eo pid,ppid,args` snapshot; injected in tests. */\n psSnapshot?: () => string;\n /** Sync existence probe for binary detection; injected in tests. */\n fileExists?: (path: string) => boolean;\n /** Read ~/.claude.json contents (raw); returns null when absent. Injected in tests. */\n readClaudeJson?: (path: string) => string | null;\n /** Resolve the user's home directory; injected in tests. */\n homeDir?: () => string;\n /** Resolve symlinks to a real path (throws when absent); injected in tests. */\n realPath?: (path: string) => string;\n /** Security floor override; defaults to CLAUDE_CODE_SECURITY_FLOOR. Injected in tests. */\n securityFloor?: string;\n}\n\nexport type UpdaterOutcome =\n | { action: 'throttled' }\n | { action: 'up_to_date'; version: string }\n | { action: 'deferred_busy'; target: string }\n | { action: 'skipped_native_install'; target: string }\n | { action: 'updated'; from: string | null; to: string }\n | { action: 'failed'; reason: string };\n\n// ── Host-global state (module-level; one updater per host process) ──────────\nlet lastCheckAt = 0;\n/** Per-process one-shot guard for the multi-install warn line. */\nlet multiInstallWarned = false;\n/**\n * Sticky counter for consecutive `post-install verify mismatch` against the\n * same target version. Reset on success, on target change, or after the\n * threshold error has been emitted (so we emit it exactly once per stuck run).\n */\nlet verifyMismatchState: { target: string; count: number; alarmed: boolean } | null = null;\n/**\n * When a below-floor host first deferred because it was busy. The\n * FORCED_UPDATE_HARD_DEADLINE_MS clock runs from here; cleared once the host\n * is at or above the floor. Process-local on purpose: a manager restart\n * restarts the clock, which costs at most one more deadline period.\n */\nlet belowFloorBusySince: number | null = null;\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetClaudeCodeUpdaterState(): void {\n lastCheckAt = 0;\n multiInstallWarned = false;\n verifyMismatchState = null;\n belowFloorBusySince = null;\n}\n\nfunction defaultPsSnapshot(): string {\n return execFileSync('ps', ['-eo', 'pid,ppid,args'], { encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024 });\n}\n\nfunction defaultReadClaudeJson(path: string): string | null {\n try {\n return readFileSync(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/** True when at least one `claude --name agt-*` agent process is live. */\nexport function hostHasLiveAgent(psOutput: string): boolean {\n return parsePsRows(psOutput).some((r) => CLAUDE_AGENT_PROCESS_PATTERN.test(r.args));\n}\n\n/**\n * ENG-5798: returns true when ~/.claude.json reports `installMethod: \"native\"`,\n * meaning Anthropic's native autoupdater owns the on-disk binary. In that case\n * the brew/npm update path is actively harmful — it installs a second binary\n * the host's PATH never resolves to, and verify reads from yet a third.\n */\nexport function readsAsNativeInstall(raw: string | null): boolean {\n if (!raw) return false;\n try {\n const parsed = JSON.parse(raw) as { installMethod?: unknown };\n return parsed.installMethod === 'native';\n } catch {\n return false;\n }\n}\n\n/**\n * ENG-10620: true when `realPath` is a binary owned by Claude Code's native\n * installer, which keeps every version under `…/claude/versions/<semver>`\n * (e.g. `~/.local/share/claude/versions/2.1.274`, symlinked from\n * `~/.local/bin/claude`). An npm global resolves into\n * `…/node_modules/@anthropic-ai/claude-code/` instead.\n */\nexport function isNativeInstallPath(realPath: string): boolean {\n return /\\/claude\\/versions\\/\\d+\\.\\d+\\.\\d+[^/]*$/.test(realPath);\n}\n\n/**\n * ENG-10620: decide native ownership from the binary this host ACTUALLY runs,\n * not from `~/.claude.json`.\n *\n * `installMethod` is written by whichever `claude` last started, not by the\n * one the manager launches. On agt-aws-1 an operator shell's PATH resolved a\n * native `~/.local/bin/claude` while agents ran the npm global at\n * `/usr/bin/claude`; that one manual run flipped `installMethod` to `native`\n * and this updater stopped converging the host to its pin for good, while the\n * binary it would have updated was never native at all.\n *\n * Resolution mirrors how agent sessions pick their binary (resolveClaudeBinary:\n * CLAUDE_PATH, then PATH). The config key remains only as a fallback for a\n * host where no binary can be resolved.\n */\nasync function resolveNativeOwnership(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n realPath: (path: string) => string,\n claudeJson: string | null,\n): Promise<{ native: boolean; binary: string | null; configSaysNative: boolean }> {\n const configSaysNative = readsAsNativeInstall(claudeJson);\n let resolved: string | null = null;\n const override = process.env['CLAUDE_PATH'];\n if (override) {\n resolved = override;\n } else {\n try {\n const { stdout } = await run('which', ['claude']);\n resolved = stdout.trim().split('\\n')[0]?.trim() || null;\n } catch {\n resolved = null;\n }\n }\n if (resolved) {\n try {\n const real = realPath(resolved);\n return { native: isNativeInstallPath(real), binary: real, configSaysNative };\n } catch {\n /* unresolvable — fall back to the config key */\n }\n }\n return { native: configSaysNative, binary: null, configSaysNative };\n}\n\n/**\n * Resolve the absolute filesystem path that `npm install -g <pkg>` will write\n * the `claude` binary to. Returns null when `npm prefix -g` fails or is empty,\n * in which case the caller should fall back to PATH-based verify.\n *\n * We deliberately use `npm prefix -g` rather than `which claude` because the\n * latter resolves through PATH, which on agt-aws-* hosts can land on\n * /usr/bin/claude (the native autoupdater slot) — exactly the misalignment\n * ENG-5798 is fixing.\n */\nasync function resolveInstallTargetPath(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n): Promise<string | null> {\n try {\n const { stdout } = await run('npm', ['prefix', '-g']);\n const prefix = stdout.trim();\n if (!prefix) return null;\n return `${prefix}/bin/claude`;\n } catch {\n return null;\n }\n}\n\n/**\n * Enumerate every `claude` binary visible to the host: PATH-resolved + the\n * well-known absolute slots + `~/.claude/local/claude`. Returns one entry per\n * unique resolved path with the version each reports (or null if it failed to\n * execute). Used purely for the one-shot drift warn line.\n */\nexport async function probeClaudeInstalls(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n fileExists: (path: string) => boolean,\n home: string,\n): Promise<Array<{ path: string; version: string | null }>> {\n const candidates = new Set<string>(WELL_KNOWN_CLAUDE_PATHS);\n candidates.add(`${home}/.claude/local/claude`);\n // PATH-resolved. `which` is portable enough across the Linux + macOS hosts\n // we target; errors here just mean \"no claude on PATH\", which is fine.\n try {\n const { stdout } = await run('which', ['claude']);\n const p = stdout.trim().split('\\n')[0]?.trim();\n if (p) candidates.add(p);\n } catch {\n /* no-op */\n }\n\n const results: Array<{ path: string; version: string | null }> = [];\n for (const p of candidates) {\n if (!fileExists(p)) continue;\n let version: string | null = null;\n try {\n const { stdout } = await run(p, ['--version']);\n version = parseVersion(stdout);\n } catch {\n /* binary exists but failed to execute — record path with null version */\n }\n results.push({ path: p, version });\n }\n return results;\n}\n\n/**\n * Bring Claude Code to the desired version if the host is idle and not already\n * on target. Safe to call every manager poll — internally throttled. Returns an\n * outcome describing what happened (for logging/observability/tests).\n */\nexport async function maybeUpdateClaudeCode(deps: ClaudeCodeUpdaterDeps): Promise<UpdaterOutcome> {\n const now = deps.now ? deps.now() : Date.now();\n const run = deps.runCommand ?? ((cmd, args) => execFileAsync(cmd, args, { maxBuffer: 16 * 1024 * 1024 }));\n const ps = deps.psSnapshot ?? defaultPsSnapshot;\n const fileExists = deps.fileExists ?? existsSync;\n const readClaudeJson = deps.readClaudeJson ?? defaultReadClaudeJson;\n const realPath = deps.realPath ?? realpathSync;\n const home = (deps.homeDir ?? homedir)();\n const { log } = deps;\n const floor = deps.securityFloor ?? CLAUDE_CODE_SECURITY_FLOOR;\n let forcedUnderLiveAgents = false;\n\n // 1. Host-global throttle.\n if (now - lastCheckAt < intervalMs()) {\n return { action: 'throttled' };\n }\n lastCheckAt = now;\n\n try {\n const spec = (deps.desiredVersion || 'latest').trim();\n\n // 2. ENG-5798 multi-install drift detection. Emit one warn line per\n // process listing every claude binary + version we can see. This is\n // independent of the install decision below — even on hosts where we\n // proceed normally, surfacing drift is the difference between a 3-day\n // silent loop and a one-line lead.\n if (!multiInstallWarned) {\n const installs = await probeClaudeInstalls(run, fileExists, home);\n if (installs.length > 1) {\n const summary = installs\n .map((i) => `${i.path}=${i.version ?? 'unknown'}`)\n .join(', ');\n log(`[claude-code-updater] multiple claude installs detected: ${summary}`);\n }\n multiInstallWarned = true;\n }\n\n // 3. ENG-5798 native-install short-circuit. When Anthropic's native\n // autoupdater owns the host binary, the brew/npm update path is\n // actively harmful (it installs a second binary the manager's PATH\n // never resolves to, then verifies against yet a third). Defer.\n // ENG-10869: the path the AGENT actually reads, not the legacy one.\n // Once this host migrates, `~/.claude.json` is ARCHIVED, so a hardcoded\n // read here returns nothing forever and `configSaysNative` is permanently\n // false — silently re-opening the ENG-5798 harm this short-circuit exists\n // to prevent (brew/npm installing a second binary on a host Anthropic's\n // native autoupdater owns). The resolver falls back to the legacy file, so\n // an unmigrated host is unaffected.\n const claudeJsonPath = effectiveClaudeJsonPath(home);\n const ownership = await resolveNativeOwnership(run, realPath, readClaudeJson(claudeJsonPath));\n if (!ownership.native && ownership.configSaysNative) {\n log(\n `[claude-code-updater] ${claudeJsonPath} says installMethod=native but the binary this host runs (${ownership.binary}) is not a native install — ignoring it and converging to the pin`,\n );\n }\n if (ownership.native) {\n // One-shot info line per process — same throttle as the warn above, but\n // we re-use the throttle window to keep this from logging every poll.\n log('[claude-code-updater] native install detected, deferring to Anthropic autoupdater');\n // Reset any sticky verify-mismatch state — it can't apply once we stop\n // attempting installs.\n verifyMismatchState = null;\n return { action: 'skipped_native_install', target: spec };\n }\n\n // 4. Resolve the target to a concrete version (read-only). A concrete spec\n // is its own target; a dist-tag is resolved via `npm view`.\n let target: string;\n if (isConcreteVersion(spec)) {\n target = spec;\n } else {\n const { stdout } = await run('npm', ['view', `${NPM_PKG}@${spec}`, 'version']);\n const resolved = parseVersion(stdout);\n if (!resolved) {\n log(`[claude-code-updater] could not resolve ${NPM_PKG}@${spec} to a version; skipping`);\n return { action: 'failed', reason: 'unresolved_target' };\n }\n target = resolved;\n }\n\n // 4b. Security floor: never converge to a version below it. A pin (or a\n // stale fleet default) under the floor would otherwise short-circuit at\n // step 6 as \"up_to_date\" and keep the host vulnerable forever.\n const targetVsFloor = compareVersions(target, floor);\n if (targetVsFloor !== null && targetVsFloor < 0) {\n log(\n `[claude-code-updater] target ${target} is below the security floor ${floor} (ENG-11641: CVE-2026-111497 + emSign root) — raising the target to the floor`,\n );\n target = floor;\n }\n\n // 5. Determine the installed version (prefer the caller's cached value).\n let current = deps.currentVersion ?? null;\n if (!current) {\n try {\n const { stdout } = await run('claude', ['--version']);\n current = parseVersion(stdout);\n } catch {\n current = null; // claude missing/broken — treat as \"needs install\".\n }\n }\n\n const belowFloor = isBelowSecurityFloor(current, floor);\n if (!belowFloor) belowFloorBusySince = null;\n\n // 6. Already on target → nothing to do.\n if (current && current === target) {\n // Successful no-op — clear sticky verify mismatch state for this target.\n if (verifyMismatchState && verifyMismatchState.target === target) {\n verifyMismatchState = null;\n }\n return { action: 'up_to_date', version: target };\n }\n\n // 7. Idle gate — never replace the binary under a live agent session.\n // Exception: a host below the security floor defers for at most\n // FORCED_UPDATE_HARD_DEADLINE_MS, then installs under live agents if the\n // npm slot is provably the running binary (see the module header; the\n // same bounded-wait shape as the forced agt-cli update, ENG-8449).\n if (hostHasLiveAgent(ps())) {\n if (!belowFloor) {\n log(`[claude-code-updater] host busy (live agent) — deferring update to ${target} to a later cycle`);\n return { action: 'deferred_busy', target };\n }\n if (belowFloorBusySince === null) belowFloorBusySince = now;\n const waitedMs = now - belowFloorBusySince;\n if (waitedMs < FORCED_UPDATE_HARD_DEADLINE_MS) {\n // Re-check in ~1 min instead of the normal 6h throttle, so the deadline\n // is actually observed when it passes.\n lastCheckAt = now - intervalMs() + BELOW_FLOOR_RECHECK_MS;\n log(\n `[claude-code-updater] host busy (live agent) and ${current} is below the security floor ${floor} — deferring update to ${target}; will force it in ${Math.ceil((FORCED_UPDATE_HARD_DEADLINE_MS - waitedMs) / 60_000)}m if the npm install slot is the binary agents run`,\n );\n return { action: 'deferred_busy', target };\n }\n // Review 97810715f303 / ENG-5798: before overriding the idle gate, check\n // the slot npm actually writes. If it is ALREADY at or above the floor,\n // the below-floor reading came from a different binary on PATH (the\n // multi-install case), so an install here cannot fix anything. Forcing it\n // would re-run `npm install -g` under live agents every cycle while the\n // PATH binary stays put. Fall back to the normal idle-gated behaviour and\n // say why.\n const slotPath = await resolveInstallTargetPath(run);\n let slotVersion: string | null = null;\n if (slotPath) {\n try {\n slotVersion = parseVersion((await run(slotPath, ['--version'])).stdout);\n } catch {\n slotVersion = null;\n }\n }\n // Review fe4300a0793c: an unresolvable slot means we cannot prove an\n // install would help either, so it gets the same fall-back.\n if (!slotPath) {\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] ERROR security_floor_blocked: deadline reached but \\`npm prefix -g\\` did not resolve — cannot prove an install would reach the binary agents run; not forcing an install under live agents`,\n );\n return { action: 'deferred_busy', target };\n }\n // Review 0a3b967ea569: only force when there is positive evidence that\n // the slot npm writes IS the binary agents run and IS below the floor:\n // its version is readable, below the floor, and equal to what PATH\n // reports. An empty/broken slot (version unreadable), a slot already at\n // or above the floor, or a slot on a different version than PATH all mean\n // an install cannot be shown to help, so fall back to the idle gate.\n const slotIsTheRunningBinary =\n slotVersion !== null && isBelowSecurityFloor(slotVersion, floor) && slotVersion === current;\n if (!slotIsTheRunningBinary) {\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] ERROR security_floor_blocked: PATH claude reports ${current} (below the security floor ${floor}) but the npm install slot ${slotPath} reports ${slotVersion ?? 'no readable version'} — cannot show the install would reach the binary agents run (multi-install, ENG-5798); not forcing an install under live agents, investigate the host's claude install slots`,\n );\n return { action: 'deferred_busy', target };\n }\n log(\n `[claude-code-updater] security floor deadline reached: ${current} < ${floor} and host still busy after ${Math.round(waitedMs / 60_000)}m — installing ${target} anyway (ENG-11641)`,\n );\n forcedUnderLiveAgents = true;\n }\n\n // 8. Install the target, then verify against the *absolute* path of the\n // binary npm just wrote — not via PATH. PATH resolution is the original\n // ENG-5798 bug: on hosts with multiple claude installs, PATH lands on a\n // different binary than the one npm overwrites, so verify never agrees.\n const installTargetPath = await resolveInstallTargetPath(run);\n log(`[claude-code-updater] updating Claude Code ${current ?? '(none)'} → ${target}`);\n // ENG-11669 (Obi Wan d623befb7109): hold the package-cache lock so the\n // disk guard's `npm cache clean` can never run under this install.\n await withPackageCacheLock(() => run('npm', ['install', '-g', `${NPM_PKG}@${target}`]), {\n label: 'the Claude Code install',\n log,\n });\n\n // Prefer the absolute install target; fall back to PATH only when\n // `npm prefix -g` failed (in which case verify is best-effort).\n const verifyTarget = installTargetPath ?? 'claude';\n let installed: string | null = null;\n try {\n const { stdout } = await run(verifyTarget, ['--version']);\n installed = parseVersion(stdout);\n } catch {\n installed = null;\n }\n\n if (installed !== target) {\n // Review fe4300a0793c: a forced install that did not verify must not keep\n // the old deadline, or every later cycle would force again immediately.\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] post-install verify mismatch: expected ${target}, got ${installed ?? 'unknown'} from ${verifyTarget}`,\n );\n\n // ENG-5798: hard-error after N consecutive mismatches against the same\n // target. Reset the counter when the target changes — a new pin is a\n // new event, not a continuation of the old stuck run.\n if (!verifyMismatchState || verifyMismatchState.target !== target) {\n verifyMismatchState = { target, count: 1, alarmed: false };\n } else {\n verifyMismatchState.count += 1;\n }\n\n const threshold = verifyMismatchThreshold();\n if (verifyMismatchState.count >= threshold && !verifyMismatchState.alarmed) {\n verifyMismatchState.alarmed = true;\n log(\n `[claude-code-updater] ERROR verify_mismatch_stuck: ${verifyMismatchState.count} consecutive verify failures for target ${target} (verify path: ${verifyTarget}) — install is not taking effect, investigate host install slots`,\n );\n return { action: 'failed', reason: 'verify_mismatch_threshold' };\n }\n return { action: 'failed', reason: 'verify_mismatch' };\n }\n\n // Successful install — clear sticky state. Also end any security-floor wait\n // episode, so a later below-floor reading starts a fresh deadline instead of\n // inheriting this one's (review 97810715f303).\n verifyMismatchState = null;\n belowFloorBusySince = null;\n log(`[claude-code-updater] updated Claude Code ${current ?? '(none)'} → ${installed}`);\n if (forcedUnderLiveAgents) {\n // Review 8f1c08ba3c77: from here `claude --version` (and so the heartbeat's\n // framework_version) reads the new version, but the live sessions that held\n // the idle gate still execute the OLD binary until they restart. Say so\n // loudly, so the host does not read as remediated when it is not. The\n // recycle of those sessions is runtime-image-recycle's job (#7134, host\n // mode: /proc/<pid>/exe reads \"(deleted)\").\n log(\n `[claude-code-updater] WARNING installed ${installed} under live agents: running sessions keep the previous ${current ?? 'unknown'} binary until they restart. framework_version now reports ${installed}, which does NOT mean the live sessions are patched`,\n );\n }\n return { action: 'updated', from: current, to: installed };\n } catch (err) {\n // Review f540008e5f47: a forced install that THREW (npm non-zero exit,\n // EACCES, registry/network error) is the commonest failed install. Like a\n // failed verify, it must not keep the old deadline.\n if (forcedUnderLiveAgents) belowFloorBusySince = null;\n // Log-and-continue: an updater failure must never break the manager tick.\n log(`[claude-code-updater] update attempt failed: ${err instanceof Error ? err.message : String(err)}`);\n return { action: 'failed', reason: 'exception' };\n }\n}\n","/**\n * ENG-11669 — keep a host's root disk from filling up, and never start a CLI\n * self-update into a full disk.\n *\n * What happened (alyve-host-1, 23 Oct 14:00:40Z): the root disk was full when\n * the manager's self-update ran `npm install -g @integrity-labs/agt-cli@…`.\n * npm removes the old package before writing the new one, the write died with\n * ENOSPC, and `/usr/bin/agt` was simply gone. The manager could not start again,\n * and six agents were dark for ~33h until a human freed disk over SSM. The\n * ENG-6698 smoke check could not help: it runs AFTER the install, and by then\n * the old binary was already deleted.\n *\n * Two defences, both here:\n * 1. `ensureDiskForSelfUpdate` - before any self-update install, make sure the\n * disk has room. If it does not, run the safe cleanup once and re-check. If\n * it still does not, refuse the install (keep the working binary) and say so.\n * 2. `maybeRunDiskGuard` - hourly, and independent of any update: when the disk\n * is low, run the same safe cleanup, so a slow fill (package caches, journal)\n * is reclaimed long before anything ENOSPCs.\n *\n * SAFE CLEANUP is deliberately narrow. Every step reclaims a CACHE or a log that\n * the system rebuilds on demand, and none of them can touch agent work:\n * - `journalctl --vacuum-size=200M` (systemd journal)\n * - `npm cache clean --force` (npm's content cache)\n * - `pnpm store prune` (unreferenced packages only, by design)\n * It never deletes files by path. In particular it never touches\n * `/root/.claude/projects` (agent transcripts, the ONLY copy of the last 7 days\n * on a host that missed its log-retention reconcile) or agent workspaces.\n *\n * Pure decision functions plus injected effects, so every branch is testable\n * without a real disk or a real `sudo`.\n */\n\nimport { readdirSync, readFileSync, statSync, statfsSync } from 'node:fs';\nimport { execFileSync } from 'node:child_process';\n\nconst GIB = 1024 * 1024 * 1024;\n\n/** Free/total bytes on a filesystem, as seen by an unprivileged writer. */\nexport interface DiskStat {\n freeBytes: number;\n totalBytes: number;\n}\n\n/**\n * The disk is LOW below 10% free, with a 2 GiB floor for small disks (on 8 GB,\n * 10% would be too tight to be useful).\n *\n * ENG-11706: there is deliberately NO upper cap any more. ENG-11669 capped the\n * threshold at 5 GiB (Obi Wan 97b2c0d967a7) so a big, moderately full disk was\n * not \"low\" forever. On agt-aws-1 (27 Oct) that cap is what let a 100 GB root\n * disk sit at 94% all night with the 85% CloudWatch alarm red and the guard\n * reading \"ok\", because 6 GB free was above 5 GiB. Brad's call: trigger at\n * 10%. The cost the cap was avoiding is small: when a disk really is under\n * 10% free, an hourly journal vacuum and cache prune are cheap, and a STILL\n * LOW line every hour is a true statement about a disk that needs a human.\n *\n * Per-agent caches are NOT cleaned here on purpose: they live in a tree the\n * agent's container can write to, so host root running npm/pnpm there can be\n * redirected by a symlink (Obi Wan 1fee2019cee5 on #7177). Each agent cleans\n * its own caches from inside its own container instead (ENG-11707).\n */\nexport const DISK_LOW_FREE_RATIO = 0.1;\nexport const DISK_LOW_FREE_BYTES = 2 * GIB;\n\nexport function diskLowThresholdBytes(totalBytes: number): number {\n return Math.max(DISK_LOW_FREE_BYTES, totalBytes * DISK_LOW_FREE_RATIO);\n}\n\n/**\n * Minimum free space to START a self-update install. The agt-cli package plus\n * npm's staging is a few hundred MB; 1 GiB leaves room for the install AND for\n * the agents still writing while it runs.\n */\nexport const SELF_UPDATE_MIN_FREE_BYTES = 1 * GIB;\n\n/** The hourly guard's throttle. */\nexport const DISK_GUARD_INTERVAL_MS = 60 * 60 * 1000;\n\n/** Read free/total for `path`. `null` when the stat itself fails (reported, never guessed). */\nexport function readDiskStat(path = '/', statfs: typeof statfsSync = statfsSync): DiskStat | null {\n try {\n const s = statfs(path);\n // bavail: blocks available to a NON-root writer. The manager may run as\n // root, but agents and npm's cache writers often do not, and root's reserved\n // blocks are exactly what lets a \"full\" disk limp on; measuring without them\n // is the honest number.\n const bsize = Number(s.bsize);\n return { freeBytes: Number(s.bavail) * bsize, totalBytes: Number(s.blocks) * bsize };\n } catch {\n return null;\n }\n}\n\nexport type DiskVerdict = 'ok' | 'low' | 'unknown';\n\nexport function diskVerdict(stat: DiskStat | null): DiskVerdict {\n if (!stat || !Number.isFinite(stat.freeBytes) || !Number.isFinite(stat.totalBytes) || stat.totalBytes <= 0) {\n return 'unknown';\n }\n return stat.freeBytes < diskLowThresholdBytes(stat.totalBytes) ? 'low' : 'ok';\n}\n\n/** One cleanup step: a binary and its args. Never a shell string, never a path to delete. */\nexport interface CleanupStep {\n bin: string;\n args: string[];\n /**\n * Needs root (the system journal). The npm cache and pnpm store are the\n * MANAGER USER's own, so those run as that user, never under sudo: under\n * sudo, HOME can become /root and the command would clean root's cache\n * instead of the one that is actually growing (Obi Wan c33094e187b2).\n */\n needsRoot: boolean;\n /**\n * Touches a package-manager cache that a concurrent install reads from.\n * Skipped while any npm/pnpm install is running (Obi Wan b8ed1339eaf2):\n * wiping the cache under `npm install -g @anthropic-ai/claude-code` (the\n * Claude Code updater runs in the same poll) could fail that install after\n * npm already removed the old package, the missing-binary failure again.\n */\n packageCache: boolean;\n}\n\nexport const SAFE_CLEANUP_STEPS: readonly CleanupStep[] = [\n { bin: 'journalctl', args: ['--vacuum-size=200M'], needsRoot: true, packageCache: false },\n { bin: 'npm', args: ['cache', 'clean', '--force'], needsRoot: false, packageCache: true },\n { bin: 'pnpm', args: ['store', 'prune'], needsRoot: false, packageCache: true },\n];\n\n/**\n * Obi Wan 3c3bd567504f: on a NON-root manager the self-update runs\n * `sudo -n npm install -g`, whose cache is root's when sudo resets HOME. So a\n * non-root manager cleans BOTH: its own cache (above, no sudo) and root's\n * (`sudo -n -H`, HOME forced to root's). On a root manager they are the same\n * cache and this step is not run.\n */\nexport const NON_ROOT_EXTRA_STEPS: readonly CleanupStep[] = [\n { bin: 'npm', args: ['cache', 'clean', '--force'], needsRoot: true, packageCache: true },\n];\n\n/** Async on purpose: the guard runs inside the manager's poll loop, and a synchronous\n * `npm cache clean` would freeze heartbeats for as long as it takes. */\nexport type ExecFn = (cmd: string, args: string[], opts: { timeout: number }) => Promise<void>;\n\nexport interface CleanupStepResult {\n step: string;\n outcome: 'ran' | 'missing' | 'failed' | 'skipped';\n detail?: string;\n}\n\n/**\n * Run every safe cleanup step, best-effort and independently: one failing (or\n * not installed, e.g. pnpm on a host that never used it) never stops the rest.\n * Elevates via `sudo -n` when not root, exactly like the npm self-update path.\n */\nexport async function runSafeCleanup(deps: {\n exec: ExecFn;\n isRoot: boolean;\n timeoutMs?: number;\n /**\n * Is an npm/pnpm install running right now? true, or a check that could not\n * answer, SKIPS the package-cache steps (fail safe: a skipped clean costs\n * one hour of cache; a clean under an install can cost the binary).\n */\n packageManagerBusy?: () => Promise<boolean>;\n}): Promise<CleanupStepResult[]> {\n const timeout = deps.timeoutMs ?? CLEANUP_STEP_TIMEOUT_MS;\n const results: CleanupStepResult[] = [];\n const steps = deps.isRoot ? SAFE_CLEANUP_STEPS : [...SAFE_CLEANUP_STEPS, ...NON_ROOT_EXTRA_STEPS];\n for (const step of steps) {\n const label = `${step.needsRoot && !deps.isRoot ? 'sudo ' : ''}${step.bin} ${step.args.join(' ')}`;\n if (step.packageCache) {\n // Re-checked before EVERY package-cache step (Obi Wan d623befb7109): a\n // step can take minutes, and an install started outside this process is\n // not held off by the lock.\n const busy = deps.packageManagerBusy ? await deps.packageManagerBusy().catch(() => true) : false;\n if (busy) {\n results.push({ step: label, outcome: 'skipped', detail: 'an npm/pnpm install is running' });\n continue;\n }\n }\n const [cmd, args] =\n deps.isRoot || !step.needsRoot\n ? [step.bin, step.args]\n : ['sudo', [...(step.bin === 'journalctl' ? ['-n'] : ['-n', '-H']), step.bin, ...step.args]];\n try {\n await deps.exec(cmd, args, { timeout });\n results.push({ step: label, outcome: 'ran' });\n } catch (err) {\n const e = err as NodeJS.ErrnoException & { status?: number; stderr?: Buffer | string };\n const stderr = e.stderr ? String(e.stderr) : '';\n // ENOENT (no such binary) as root, or sudo's \"command not found\" as non-root.\n const missing = e.code === 'ENOENT' || /command not found|not found/i.test(stderr);\n results.push({\n step: label,\n outcome: missing ? 'missing' : 'failed',\n detail: (stderr || e.message || '').trim().slice(0, 200),\n });\n }\n }\n return results;\n}\n\nfunction mb(bytes: number): string {\n return `${Math.round(bytes / (1024 * 1024))}MB`;\n}\n\nfunction summarize(results: CleanupStepResult[]): string {\n return results.map((r) => `${r.step}=${r.outcome}`).join(', ');\n}\n\nexport interface DiskGuardDeps {\n readStat: () => DiskStat | null;\n cleanup: () => Promise<CleanupStepResult[]>;\n log: (line: string) => void;\n}\n\nexport type SelfUpdateRoom =\n /**\n * `release` MUST be called once the install has finished (or failed): the\n * self-update holds the package-cache lock from its checks through its install,\n * so nothing can start a cleanup or another npm install in between\n * (Obi Wan a2c6f266bf18).\n */\n | { ok: true; freeBytes: number | null; cleaned: boolean; release: () => void }\n | { ok: false; freeBytes: number; cleaned: true }\n /** ENG-11669 (Obi Wan 84656ed7dfce): held because the package cache stayed busy; disk not measured. */\n | { ok: false; freeBytes: null; cleaned: false; reason: 'package_cache_busy' };\n\n/**\n * ENG-11669: is there room to START a self-update install? Called immediately\n * before the install, the only point at which the answer is current.\n *\n * - Enough room: go.\n * - Not enough: run the safe cleanup once, re-measure, go if it worked.\n * - Still not enough: REFUSE. The caller keeps the working binary and retries\n * on a later cycle; a refused update costs a version, an ENOSPC mid-install\n * costs the host.\n * - Unreadable stat: go, as before this guard existed. Failing closed on an\n * unreadable statfs would freeze every update on a host with an odd mount,\n * which is a worse outcome than the one this guards against; it is logged.\n */\n/**\n * ENG-11669 (Obi Wan 7c1384a483ca): hold-episode state. While a host is held,\n * the self-update check still runs every few minutes; without this every one of\n * them would re-run a cleanup that already failed and re-log the HELD line,\n * roughly 288 times a day into a disk that is already full. Same shape as the\n * ENG-10724 device-state hold's latch: one HELD line per episode, cleanup\n * retried at most once per DISK_GUARD_INTERVAL_MS, both reset the moment there\n * is room again.\n */\nlet holdEpisode: { lastCleanupMs: number } | null = null;\nlet busyEpisode: { since: number; loggedAt: number } | null = null;\nexport function resetSelfUpdateHoldEpisode(): void {\n holdEpisode = null;\n busyEpisode = null;\n forcedHoldAnnounced = false;\n forcedBusyAnnounced = false;\n}\n\n/**\n * Obi Wan 39885fe919e4: a held forced \"Update CLI now\" is re-kicked every\n * update check (~5 min) until it succeeds, each time with force=true, so the\n * forced HELD line is latched like the device-state hold's: said once per\n * forced REQUEST, re-armed by the caller when a new request arrives\n * (rearmForcedDiskHoldAnnounce, wired next to selfUpdateForcedHoldAnnounced).\n */\nlet forcedHoldAnnounced = false;\n/** Obi Wan 253abefc6771: a forced request held on a BUSY package cache is told why, once per request. */\nlet forcedBusyAnnounced = false;\nexport function rearmForcedDiskHoldAnnounce(): void {\n forcedHoldAnnounced = false;\n forcedBusyAnnounced = false;\n}\n\n/**\n * One lock around everything that reads or wipes the package caches: the safe\n * cleanup, and package installs (the Claude Code updater's `npm install -g`).\n * Obi Wan 7a21830b25c1 / d623befb7109: the hourly guard runs fire-and-forget\n * and an async install can start while it runs, so a single up-front \"is an\n * install running?\" check is not enough. Holders run strictly one at a time,\n * in arrival order. The agt self-update check waits for the lock to go idle\n * before it measures and returns; its install is synchronous (execFileSync\n * blocks the event loop), so nothing can start a cleanup under it.\n */\nlet cacheLockTail: Promise<void> = Promise.resolve();\nlet cacheLockHolders = 0;\n\n/**\n * Obi Wan 84656ed7dfce: no holder may own the lock forever. A stalled\n * `npm install -g` (a registry hang) must not park the agt self-update, the\n * host's remediation channel, behind it indefinitely. After this long the lock\n * is RELEASED (logged) even if the holder is still running; the holder itself\n * is not killed, and anything queued may then proceed. Both remaining sides\n * re-check with pgrep: the cleanup before each package-cache step, and the agt\n * self-update before it starts its install (Obi Wan 59205b9de2aa).\n */\nexport const PACKAGE_CACHE_LOCK_MAX_HOLD_MS = 10 * 60 * 1000;\n/** How long the agt self-update check waits for the lock before holding instead. */\n// Obi Wan 5e64cc74a59a / 128538f49206: the self-update's bounded wait to\n// ACQUIRE the lock. See SELF_UPDATE_LOCK_MAX_HOLD_MS for the full budget.\nexport const PACKAGE_CACHE_IDLE_MAX_WAIT_MS = 5 * 60 * 1000;\n/** Per-step timeout for the safe cleanup. */\nexport const CLEANUP_STEP_TIMEOUT_MS = 120_000;\n/**\n * The self-update holds the lock across measure -> cleanup -> install. Worst\n * case inside the hold: cleanup of 4 steps x 120s (non-root) = 8 min, plus the\n * install (npm 180s timeout) = 11 min, so a 12 min max hold.\n *\n * Against the 20 min self-update latch (Obi Wan 128538f49206), which is taken\n * BEFORE the pre-install network checks: brew update + outdated (60s + 30s)\n * = 1.5 min, then the 5 min acquire bound, then the 12 min hold, then the 15s\n * smoke check = 18.75 min worst case. Inside 20.\n */\nexport const SELF_UPDATE_LOCK_MAX_HOLD_MS = 12 * 60 * 1000;\n/**\n * The self-update's own command timeouts, shared with manager-worker.ts so the\n * latch budget test reads the values that actually run (Obi Wan 6909f9596353).\n */\nexport const SELF_UPDATE_TIMEOUTS = {\n brewUpdateMs: 60_000,\n brewOutdatedMs: 30_000,\n brewUpgradeMs: 120_000,\n npmInstallMs: 180_000,\n smokeCheckMs: 15_000,\n} as const;\n/**\n * Obi Wan 51f6036d7808: the longest the self-update defers to \"another install\n * is running\". Past it the install goes ahead (logged): a looping, hung or\n * hostile install must not hold the host's remediation channel forever. Ours is\n * still serialised by the package-cache lock; this only stops deferring to\n * processes outside it.\n */\nexport const SELF_UPDATE_BUSY_CEILING_MS = 2 * 60 * 60 * 1000;\n/** Busy holds are logged once, then re-said (as a warning) at most this often. */\nexport const BUSY_HOLD_RELOG_MS = 60 * 60 * 1000;\n\nconst INSTALL_VERBS = new Set(['install', 'i', 'ci', 'add', 'update', 'upgrade', 'up']);\nconst NOT_AN_INSTALL = new Set(['run', 'run-script', 'exec', 'x', 'dlx', 'test', 'start']);\nconst PM_BINS = /^(npm|pnpm)$/;\nconst PM_SCRIPTS = /^(npm|npm-cli\\.js|pnpm|pnpm\\.c?js)$/;\nconst basenameOf = (p: string) => p.slice(p.lastIndexOf('/') + 1);\n\n/**\n * Obi Wan 106f4019f3b9: is THIS process a package-manager install? Only when\n * the process itself is npm/pnpm (argv[0], or node running the npm/pnpm\n * script) and its first subcommand is an install verb. A shell whose command\n * line merely mentions one (`bash -c \"npm install && npm run dev\"`, alive for\n * the life of the dev server) is the shell, not an install, and does not count;\n * its npm child does, for exactly as long as that child runs.\n */\nexport function isPackageInstallArgv(argv: readonly string[]): boolean {\n let args = argv.filter((a) => a.length > 0);\n // npm overwrites its process title (\"npm install\"), which reads back as one\n // space-joined argv entry.\n if (args.length === 1 && args[0]!.includes(' ')) args = args[0]!.split(/\\s+/);\n if (args.length === 0) return false;\n let rest: string[];\n const bin = basenameOf(args[0]!);\n if (PM_BINS.test(bin)) rest = args.slice(1);\n else if (/^node(js)?$/.test(bin) && args[1] && PM_SCRIPTS.test(basenameOf(args[1]))) rest = args.slice(2);\n else return false;\n const sub = rest.find((a) => !a.startsWith('-'));\n if (!sub || NOT_AN_INSTALL.has(sub)) return false;\n if (INSTALL_VERBS.has(sub)) return true;\n // A flag value before the subcommand (`npm --prefix /x install`): look one further.\n const next = rest.slice(rest.indexOf(sub) + 1).find((a) => !a.startsWith('-'));\n return next !== undefined && INSTALL_VERBS.has(next);\n}\n\n/**\n * Host-wide: is any npm/pnpm install running that could collide with ours?\n *\n * - Linux: read /proc. A process whose ROOT FILESYSTEM is not ours (an agent's\n * container) is skipped: its npm cache and global prefix live on a different\n * filesystem, so it cannot race our cleanup or our `npm install -g` (Obi Wan\n * e43cffc301c5 / 51f6036d7808). Identity is the dev:ino of /proc/<pid>/root,\n * NOT the mount namespace (Obi Wan ee7fd03f9843): a systemd unit with\n * PrivateTmp/ProtectSystem, a snap or an SSM session can sit in its own\n * mount namespace while sharing the host's root, and those still count. A\n * root we cannot stat is counted (fail safe); a pid that vanished mid-scan is\n * skipped. Statting another uid's /proc/<pid>/root needs ptrace access: the\n * fleet's manager runs as root (agt-manager.service, User=root by default),\n * so it can. A non-root manager would count other uids' container installs\n * as busy, which costs at most SELF_UPDATE_BUSY_CEILING_MS of deferral (Obi\n * Wan 7e45e4c1a576).\n * - No /proc (macOS, Obi Wan eff7b7bd11f3): fall back to `ps -A -o args=`, the\n * space-joined argv of every process. Install argv has no spaces inside its\n * tokens in practice, so splitting on whitespace is good enough here.\n * - Neither readable: throws, which the callers read as busy. That is bounded:\n * the self-update stops deferring after SELF_UPDATE_BUSY_CEILING_MS.\n */\nexport function anyPackageInstallRunning(\n deps: {\n listPids?: () => string[];\n readCmdline?: (pid: string) => string | null;\n /** dev:ino of the process's root directory; null when it cannot be read. */\n readRootId?: (pid: string) => string | null;\n listProcessArgs?: () => string[];\n selfPid?: number;\n } = {},\n): boolean {\n let pids: string[] | null;\n try {\n pids = (deps.listPids ?? (() => readdirSync('/proc').filter((d) => /^\\d+$/.test(d))))();\n } catch (err) {\n if ((err as { code?: string }).code !== 'ENOENT') throw err;\n pids = null; // no /proc at all\n }\n if (pids === null) {\n const lines = (deps.listProcessArgs ?? defaultPsArgs)();\n return lines.some((line) => isPackageInstallArgv(line.trim().split(/\\s+/)));\n }\n const readCmdline =\n deps.readCmdline ??\n ((pid: string) => {\n try {\n return readFileSync(`/proc/${pid}/cmdline`, 'utf8');\n } catch {\n return null;\n }\n });\n const readRootId =\n deps.readRootId ??\n ((pid: string) => {\n try {\n const st = statSync(`/proc/${pid}/root/`);\n return `${st.dev}:${st.ino}`;\n } catch {\n return null;\n }\n });\n const self = String(deps.selfPid ?? process.pid);\n const selfRoot = readRootId('self');\n for (const pid of pids) {\n if (pid === self) continue;\n const raw = readCmdline(pid);\n if (!raw || !isPackageInstallArgv(raw.split('\\0'))) continue;\n const root = readRootId(pid);\n if (selfRoot && root && root !== selfRoot) continue; // another container's root filesystem\n return true;\n }\n return false;\n}\n\nfunction defaultPsArgs(): string[] {\n return execFileSync('ps', ['-A', '-o', 'args='], { encoding: 'utf8', timeout: 5_000 }).split('\\n').filter(Boolean);\n}\n\n/**\n * Acquire the package-cache lock, waiting at most `maxWaitMs` (default: no\n * limit). Returns a release function, or null when the wait bound is hit. A\n * waiter that gives up keeps its queue slot but releases it the instant it is\n * reached, so it never blocks anyone behind it. Every acquisition is\n * force-released after `maxHoldMs` (logged) so no holder owns it forever.\n */\nexport async function acquirePackageCacheLock(\n opts: { maxWaitMs?: number; maxHoldMs?: number; label?: string; log?: (line: string) => void } = {},\n): Promise<(() => void) | null> {\n const prev = cacheLockTail;\n let releaseTail!: () => void;\n cacheLockTail = new Promise<void>((r) => (releaseTail = r));\n\n let abandoned = false;\n let waitTimer: ReturnType<typeof setTimeout> | undefined;\n const acquired = await new Promise<boolean>((resolve) => {\n void prev.then(() => {\n if (abandoned) {\n releaseTail(); // reached our slot after giving up: pass it straight on\n return;\n }\n if (waitTimer) clearTimeout(waitTimer);\n resolve(true);\n });\n if (opts.maxWaitMs !== undefined) {\n waitTimer = setTimeout(() => {\n abandoned = true;\n resolve(false);\n }, opts.maxWaitMs);\n (waitTimer as { unref?: () => void }).unref?.();\n }\n });\n if (!acquired) return null;\n\n cacheLockHolders += 1;\n let released = false;\n const maxHold = opts.maxHoldMs ?? PACKAGE_CACHE_LOCK_MAX_HOLD_MS;\n const releaseOnce = () => {\n if (released) return;\n released = true;\n clearTimeout(holdTimer);\n cacheLockHolders -= 1;\n releaseTail();\n };\n const holdTimer = setTimeout(() => {\n (opts.log ?? console.warn)(\n `[disk-guard] package-cache lock held ${Math.round(maxHold / 60_000)}m by ${opts.label ?? 'a holder'} that is still running; ` +\n 'releasing it so a stalled install cannot block the self-update or cleanup forever [ENG-11669]',\n );\n releaseOnce();\n }, maxHold);\n (holdTimer as { unref?: () => void }).unref?.();\n return releaseOnce;\n}\n\nexport async function withPackageCacheLock<T>(\n fn: () => Promise<T>,\n opts: { maxHoldMs?: number; label?: string; log?: (line: string) => void } = {},\n): Promise<T> {\n const release = (await acquirePackageCacheLock(opts))!;\n try {\n return await fn();\n } finally {\n release();\n }\n}\nasync function exclusiveCleanup(cleanup: () => Promise<CleanupStepResult[]>): Promise<CleanupStepResult[]> {\n return withPackageCacheLock(cleanup, { label: 'the disk-guard cleanup' });\n}\n/** Test seam: drop all lock state (a test that leaks a holder must not wedge the next). */\nexport function resetPackageCacheLock(): void {\n cacheLockTail = Promise.resolve();\n cacheLockHolders = 0;\n}\n/**\n * Is anything holding the package-cache lock? A cleanup, a toolkit/updater\n * install, or the agt self-update between its disk check and its install.\n */\nexport function cleanupIsRunning(): boolean {\n return cacheLockHolders > 0;\n}\n\n/**\n * ENG-11669: is there room to START a self-update install? Called immediately\n * before the install, the only point at which the answer is current.\n *\n * - Enough room: go.\n * - Not enough: run the safe cleanup once, re-measure, go if it worked.\n * - Still not enough: REFUSE. The caller keeps the working binary and retries\n * on a later cycle; a refused update costs a version, an ENOSPC mid-install\n * costs the host. Within a hold episode the refusal is quiet and the cleanup\n * is not re-run more than hourly.\n * - Unreadable stat: go, as before this guard existed. Failing closed on an\n * unreadable statfs would freeze every update on a host with an odd mount,\n * which is a worse outcome than the one this guards against; it is logged.\n */\nexport async function ensureDiskForSelfUpdate(\n deps: DiskGuardDeps & {\n nowMs?: number;\n force?: boolean;\n idleWaitMs?: number;\n /**\n * Obi Wan 59205b9de2aa: any npm/pnpm install running on the host, whether or\n * not it took our lock (a force-released stalled one, or someone else's).\n * true, or a check that cannot answer, HOLDS the self-update.\n */\n packageManagerBusy?: () => Promise<boolean>;\n },\n): Promise<SelfUpdateRoom> {\n const busyHold = (why: 'lock' | 'pgrep'): SelfUpdateRoom => {\n // Obi Wan 106f4019f3b9: latched like the disk HELD line. Said once when the\n // busy episode starts, then at most hourly, as an escalation naming how long.\n const now = deps.nowMs ?? Date.now();\n let first = false;\n if (busyEpisode === null) {\n busyEpisode = { since: now, loggedAt: now };\n first = true;\n }\n // A forced \"Update CLI now\" has a human waiting on the answer: say why\n // once per request even inside a latched episode, like the disk hold does.\n const announceForced = deps.force === true && !forcedBusyAnnounced;\n if (announceForced) forcedBusyAnnounced = true;\n if (first || announceForced || now - busyEpisode.loggedAt >= BUSY_HOLD_RELOG_MS) {\n const heldMin = Math.round((now - busyEpisode.since) / 60_000);\n busyEpisode.loggedAt = now;\n deps.log(\n `[disk-guard] HELD${deps.force ? ' (forced \"Update CLI now\")' : ''} self-update: ` +\n (why === 'lock'\n ? 'a package install or cache cleanup has held the package cache for too long; '\n : 'another npm/pnpm install is running on this host; ') +\n 'not starting `npm install -g` alongside it. Retrying on a later check' +\n (first\n ? ' (further busy holds this episode are logged hourly)'\n : `. STILL held after ${heldMin}m: if no install should be running, find and stop the stuck npm/pnpm process`) +\n ' [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: null, cleaned: false, reason: 'package_cache_busy' };\n };\n const isBusyRaw = async () => (deps.packageManagerBusy ? await deps.packageManagerBusy().catch(() => true) : false);\n // Busy, unless we have already deferred for longer than the ceiling.\n const isBusy = async () => {\n if (!(await isBusyRaw())) {\n // Obi Wan 417034a6c12b: the ceiling bounds CONTINUOUS deferral, so an idle\n // reading ends the busy episode even when the update then holds on disk.\n busyEpisode = null;\n return false;\n }\n const now = deps.nowMs ?? Date.now();\n if (busyEpisode && now - busyEpisode.since >= SELF_UPDATE_BUSY_CEILING_MS) {\n deps.log(\n `[disk-guard] self-update has deferred to another npm/pnpm install for ${Math.round((now - busyEpisode.since) / 60_000)}m, ` +\n 'past the ceiling; proceeding anyway so a stuck or looping install cannot block updates forever [ENG-11669]',\n );\n return false;\n }\n return true;\n };\n\n // Obi Wan a2c6f266bf18: take the package-cache lock (bounded) and KEEP it from\n // these checks through the install. Nothing queued behind it, our own cleanup\n // included, can start between \"there is room and nothing else is installing\"\n // and the install itself. The caller releases it after the install.\n const release = await acquirePackageCacheLock({\n maxWaitMs: deps.idleWaitMs ?? PACKAGE_CACHE_IDLE_MAX_WAIT_MS,\n maxHoldMs: SELF_UPDATE_LOCK_MAX_HOLD_MS,\n label: 'the agt self-update',\n log: deps.log,\n });\n if (!release) return busyHold('lock');\n try {\n if (await isBusy()) {\n release();\n return busyHold('pgrep');\n }\n const room = await ensureDiskForSelfUpdateLocked(deps);\n if (!room.ok) {\n release();\n return room;\n }\n // Re-checked last: an install outside the lock may have started meanwhile.\n if (await isBusy()) {\n release();\n return busyHold('pgrep');\n }\n busyEpisode = null;\n return { ...room, release };\n } catch (err) {\n release();\n throw err;\n }\n}\n\n/** The disk decision, run while the caller holds the package-cache lock. */\nasync function ensureDiskForSelfUpdateLocked(\n deps: DiskGuardDeps & { nowMs?: number; force?: boolean },\n): Promise<{ ok: true; freeBytes: number | null; cleaned: boolean } | Exclude<SelfUpdateRoom, { ok: true }>> {\n const nowMs = deps.nowMs ?? Date.now();\n const before = deps.readStat();\n if (!before) {\n deps.log('[disk-guard] could not read free space on the install filesystem; proceeding with the self-update unchecked [ENG-11669]');\n return { ok: true, freeBytes: null, cleaned: false };\n }\n if (before.freeBytes >= SELF_UPDATE_MIN_FREE_BYTES) {\n if (holdEpisode) deps.log(`[disk-guard] self-update hold cleared: ${mb(before.freeBytes)} free [ENG-11669]`);\n holdEpisode = null;\n forcedHoldAnnounced = false;\n return { ok: true, freeBytes: before.freeBytes, cleaned: false };\n }\n\n // Already held this episode and cleaned recently: refuse quietly. Cleaning\n // again would just repeat a cleanup that already did not make room.\n if (holdEpisode && nowMs - holdEpisode.lastCleanupMs < DISK_GUARD_INTERVAL_MS) {\n // A forced \"Update CLI now\" is a human waiting on an answer, and human-paced,\n // so it is always said (Obi Wan b07e89e3f0a2; the device-state hold's\n // forced announce is the precedent). The scheduled checks stay quiet.\n if (deps.force && !forcedHoldAnnounced) {\n forcedHoldAnnounced = true;\n deps.log(\n `[disk-guard] HELD (forced \"Update CLI now\"): only ${mb(before.freeBytes)} free for the install ` +\n `(need ${mb(SELF_UPDATE_MIN_FREE_BYTES)}); cleanup already ran this hour and did not make room. ` +\n 'Free disk on this host; the request stays pending and applies once there is room ' +\n '(said once per request) [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: before.freeBytes, cleaned: true };\n }\n\n const firstHold = holdEpisode === null;\n if (firstHold) {\n deps.log(\n `[disk-guard] only ${mb(before.freeBytes)} free for the install (need ${mb(SELF_UPDATE_MIN_FREE_BYTES)} to self-update); ` +\n 'running safe cleanup first [ENG-11669]',\n );\n }\n // We already hold the lock: call the cleanup directly (exclusiveCleanup would\n // queue behind ourselves).\n const results = await deps.cleanup();\n const after = deps.readStat();\n const freeAfter = after?.freeBytes ?? before.freeBytes;\n if (freeAfter >= SELF_UPDATE_MIN_FREE_BYTES) {\n deps.log(`[disk-guard] cleanup freed space: ${mb(before.freeBytes)} -> ${mb(freeAfter)} (${summarize(results)}) [ENG-11669]`);\n holdEpisode = null;\n return { ok: true, freeBytes: freeAfter, cleaned: true };\n }\n holdEpisode = { lastCleanupMs: nowMs };\n if (!firstHold && deps.force && !forcedHoldAnnounced) {\n forcedHoldAnnounced = true;\n // Obi Wan e3f6b7a31ff4: a forced update that lands on the hourly retry is\n // refused here too, and must say so like the early-return branch does.\n deps.log(\n `[disk-guard] HELD (forced \"Update CLI now\"): still only ${mb(freeAfter)} free after cleanup ` +\n `(need ${mb(SELF_UPDATE_MIN_FREE_BYTES)}; ${summarize(results)}). Free disk on this host, then retry [ENG-11669]`,\n );\n }\n if (firstHold) {\n deps.log(\n `[disk-guard] HELD self-update: still only ${mb(freeAfter)} free after cleanup (${summarize(results)}). ` +\n 'Keeping the current agt binary rather than risk an install that deletes it and then fails with ENOSPC. ' +\n 'Free disk on this host (or grow the volume) and the update applies on a later check. ' +\n '(further holds this episode are not logged) [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: freeAfter, cleaned: true };\n}\n\n/**\n * ENG-11669 (Obi Wan 16f6c25922ae): the stat that bounds an install is the\n * TIGHTER of `/` and the filesystem the install writes to (the npm global\n * prefix or the brew Cellar). On EC2 hosts both are `/`; a host with the prefix\n * on its own mount must not be judged by the wrong disk. Unreadable paths are\n * skipped; all unreadable is null (caller proceeds, logged).\n */\nexport function tightestDiskStat(paths: string[], read: (p: string) => DiskStat | null = readDiskStat): DiskStat | null {\n let best: DiskStat | null = null;\n for (const p of [...new Set(paths)]) {\n const s = read(p);\n if (s && (best === null || s.freeBytes < best.freeBytes)) best = s;\n }\n return best;\n}\n\n/** Module state for the hourly guard. Exported reset for tests. */\nlet lastGuardRunMs: number | null = null;\nexport function resetDiskGuardThrottle(): void {\n lastGuardRunMs = null;\n guardBlindLogged = false;\n}\n\nexport type DiskGuardRun =\n | { ran: false; reason: 'throttled' | 'ok' | 'unknown' }\n | { ran: true; beforeBytes: number; afterBytes: number | null; stillLow: boolean };\n\n/**\n * ENG-11669: the hourly guard. Cheap when healthy (one statfs per hour); only\n * shells out when the disk is actually low. Logs once per run that cleans.\n */\nlet guardBlindLogged = false;\n\nexport async function maybeRunDiskGuard(deps: DiskGuardDeps & { nowMs: number }): Promise<DiskGuardRun> {\n if (lastGuardRunMs !== null && deps.nowMs - lastGuardRunMs < DISK_GUARD_INTERVAL_MS) {\n return { ran: false, reason: 'throttled' };\n }\n lastGuardRunMs = deps.nowMs;\n const before = deps.readStat();\n const verdict = diskVerdict(before);\n if (verdict === 'unknown' || !before) {\n // Obi Wan 21988ab455e0: a blind guard must say so, once per blind spell, or\n // \"healthy\" and \"cannot see\" read the same in manager.log.\n if (!guardBlindLogged) {\n guardBlindLogged = true;\n deps.log(\n '[disk-guard] could not read free space on /; the hourly disk guard is blind (no cleanup, no host_disk_low) ' +\n 'until it can. Said once until the reading comes back [ENG-11669]',\n );\n }\n return { ran: false, reason: 'unknown' };\n }\n if (guardBlindLogged) {\n guardBlindLogged = false;\n deps.log('[disk-guard] free space on / is readable again; the hourly disk guard is back [ENG-11669]');\n }\n if (verdict !== 'low') return { ran: false, reason: 'ok' };\n\n const results = await exclusiveCleanup(deps.cleanup);\n const after = deps.readStat();\n const stillLow = diskVerdict(after) === 'low';\n deps.log(\n `[disk-guard] / was low (${mb(before.freeBytes)} free of ${mb(before.totalBytes)}); safe cleanup ran ` +\n `(${summarize(results)}); now ${after ? mb(after.freeBytes) : 'unknown'} free.` +\n (stillLow\n ? ' STILL LOW: something other than caches is filling this disk. Investigate before it fills: ' +\n '`du -xh --max-depth=2 / | sort -rh | head`. Never delete /root/.claude/projects (agent transcripts). [ENG-11669]'\n : ' [ENG-11669]'),\n );\n return { ran: true, beforeBytes: before.freeBytes, afterBytes: after?.freeBytes ?? null, stillLow };\n}\n"],"mappings":"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","names":["execFileSync","readFileSync","mb","execFileSync","readFileSync"]}
@@ -8078,7 +8078,7 @@ function exchangeFailureKind(err) {
8078
8078
  }
8079
8079
 
8080
8080
  // src/lib/api-client.ts
8081
- var agtCliVersion = true ? "0.28.1157" : "dev";
8081
+ var agtCliVersion = true ? "0.28.1158" : "dev";
8082
8082
  var lastConfigHash = null;
8083
8083
  function setConfigHash(hash) {
8084
8084
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -13235,4 +13235,4 @@ export {
13235
13235
  managerInstallSystemUnitCommand,
13236
13236
  managerUninstallSystemUnitCommand
13237
13237
  };
13238
- //# sourceMappingURL=chunk-JLNUGWCP.js.map
13238
+ //# sourceMappingURL=chunk-VYBTCFQQ.js.map
@@ -12,7 +12,7 @@ import {
12
12
  probeClaudeInstalls,
13
13
  readsAsNativeInstall,
14
14
  shouldRunClaudeCodeUpdater
15
- } from "./chunk-7NAL24SM.js";
15
+ } from "./chunk-3DISC7BU.js";
16
16
  import "./chunk-XWVM4KPK.js";
17
17
  import "./chunk-D55RMQTG.js";
18
18
  import "./chunk-EL5MWGGH.js";
@@ -32,4 +32,4 @@ export {
32
32
  readsAsNativeInstall,
33
33
  shouldRunClaudeCodeUpdater
34
34
  };
35
- //# sourceMappingURL=claude-code-updater-G7CSYMS3.js.map
35
+ //# sourceMappingURL=claude-code-updater-DXLSLDHW.js.map
@@ -77,7 +77,7 @@ import {
77
77
  setConfigHash,
78
78
  shellGuard,
79
79
  tripClass
80
- } from "../chunk-JLNUGWCP.js";
80
+ } from "../chunk-VYBTCFQQ.js";
81
81
  import {
82
82
  CLAUDE_CODE_SECURITY_FLOOR,
83
83
  SELF_UPDATE_TIMEOUTS,
@@ -90,7 +90,7 @@ import {
90
90
  runSafeCleanup,
91
91
  tightestDiskStat,
92
92
  withPackageCacheLock
93
- } from "../chunk-7NAL24SM.js";
93
+ } from "../chunk-3DISC7BU.js";
94
94
  import {
95
95
  getProjectDir as getProjectDir2,
96
96
  getReadyTasks,
@@ -15807,7 +15807,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
15807
15807
  var lastVersionCheckAt = 0;
15808
15808
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
15809
15809
  var lastResponsivenessProbeAt = 0;
15810
- var agtCliVersion = true ? "0.28.1157" : "dev";
15810
+ var agtCliVersion = true ? "0.28.1158" : "dev";
15811
15811
  function resolveBrewPath(execFileSync3) {
15812
15812
  try {
15813
15813
  const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -17947,7 +17947,7 @@ async function pollCycleInner() {
17947
17947
  setConfigHash(hbResp?.config_hash ?? null);
17948
17948
  try {
17949
17949
  const desiredClaudeCodeVersion = hbResp?.desired_claude_code_version?.trim();
17950
- const { maybeUpdateClaudeCode, shouldRunClaudeCodeUpdater } = await import("../claude-code-updater-G7CSYMS3.js");
17950
+ const { maybeUpdateClaudeCode, shouldRunClaudeCodeUpdater } = await import("../claude-code-updater-DXLSLDHW.js");
17951
17951
  claudeUpdaterInFlight = true;
17952
17952
  if (desiredClaudeCodeVersion && (claudePairLoop.busy || claudePairingInProgress())) {
17953
17953
  log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.1157",
3
+ "version": "0.28.1158",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/claude-code-updater.ts","../src/lib/disk-guard.ts"],"sourcesContent":["// ENG-5672: idle-gated, server-pinned Claude Code auto-updater.\n// ENG-5798: multi-install drift handling — detect coexisting claude binaries,\n// short-circuit on native autoupdater hosts, verify against absolute\n// install path, hard-error after N consecutive verify mismatches.\n//\n// Hosts install @anthropic-ai/claude-code once at bootstrap (host-bootstrap.ts)\n// and never refresh it, so a long-lived host runs a stale Claude Code forever.\n// This module brings a host to its *desired* version, which the server pins via\n// /host/refresh (per-host override, else fleet default, else 'latest').\n//\n// Safety properties (this runs npm install -g on live prod hosts):\n// • Idle-gated — we never install while a `claude --name agt-*` agent process\n// is running on the host (a running process holds the old binary anyway;\n// replacing it under a live session is pointless and risky). Busy → defer\n// to a later cycle.\n// • No-op when already on target — a concrete target equal to the installed\n// version short-circuits; a dist-tag ('latest') is resolved to a concrete\n// version via `npm view` and compared, so we don't reinstall every cycle.\n// • Host-global throttle — at most one check per interval (default 6h), so the\n// manager's ~10-30s poll doesn't shell out to npm/ps constantly.\n// • Log-and-continue — every failure is logged and swallowed; an error here\n// must never break the manager poll cycle.\n// • Multi-install awareness (ENG-5798) — on hosts where Anthropic's native\n// autoupdater owns /usr/bin/claude, this updater short-circuits cleanly\n// instead of fighting it. On hosts where multiple binaries coexist (e.g. a\n// brew cask alongside the npm global), we log every path+version once so\n// drift is visible in manager.log without spamming every cycle.\n//\n// The *installed* version is reported separately as hosts.framework_version\n// (the claude-code framework adapter's getVersion() == `claude --version`), so\n// this module does not report it itself.\n\nimport { execFile, execFileSync } from 'node:child_process';\nimport { existsSync, readFileSync, realpathSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { promisify } from 'node:util';\nimport { effectiveClaudeJsonPath } from './claude-config-location.js';\nimport { parsePsRows, CLAUDE_AGENT_PROCESS_PATTERN } from './orphan-channel-mcp-reaper.js';\nimport { FORCED_UPDATE_HARD_DEADLINE_MS } from '@augmented/core';\nimport { withPackageCacheLock } from './disk-guard.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst NPM_PKG = '@anthropic-ai/claude-code';\n\n/**\n * ENG-11641 follow-up: the minimum host Claude Code version, for security.\n *\n * The npm package is a native binary with its own bundled Bun, so patching\n * system Node does nothing for it. Versions before 2.1.308 bundle a Bun whose\n * Brotli decoder predates the CVE-2026-111497 (Node2Shell) fix, and 2.1.280 /\n * 2.1.296 also ship the compromised emSign ECC Root CA - G3 in their built-in\n * trust store (eMudhra intermediate key compromise, oss-security 2026-10-20).\n * #7108 fixed the agt-runtime IMAGE; it did nothing for the host's own global\n * install, which #7108 measured at 2.1.280 on 22 of 24 prod hosts. Those hosts\n * never converge because this updater only runs inside the maintenance window\n * AND only when no agent is live, and a working fleet is never idle.\n *\n * A host below this floor therefore:\n * • has its target raised to the floor when the pin/fleet default is lower\n * (a pin below a security floor IS the vulnerability, so it is overridden\n * and logged, not honoured);\n * • bypasses the maintenance window (see manager-worker's call site);\n * • still defers while an agent is live, for at most\n * FORCED_UPDATE_HARD_DEADLINE_MS (the same deadline the \"Update CLI now\"\n * forced update uses, ENG-8449), then installs under live agents ONLY IF\n * the npm install slot is provably the binary agents run (its version is\n * readable, below the floor and equal to PATH's). Otherwise — unresolvable\n * slot, unreadable slot, slot already fixed, or slot on a different version\n * (the ENG-5798 multi-install case) — it does NOT force, logs\n * `ERROR security_floor_blocked`, and the host stays below the floor until\n * an operator fixes its install slots. That is a known, alarmed limit, not\n * a guarantee of convergence.\n *\n * Raise this when a new security floor is set; never lower it.\n */\nexport const CLAUDE_CODE_SECURITY_FLOOR = '2.1.308';\n\n/** How soon to re-check a below-floor host that deferred because it was busy. */\nconst BELOW_FLOOR_RECHECK_MS = 60_000;\n\n/** Default host-global check interval. Overridable via env for tuning. */\nconst DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6h\n\n/**\n * ENG-5798: After this many consecutive verify mismatches against the same\n * target version, emit a distinct one-shot error code that the\n * synthetic-probe / CloudWatch alarm pipeline can latch onto, instead of\n * silently re-failing forever. Overridable via env for testing.\n */\nconst DEFAULT_VERIFY_MISMATCH_THRESHOLD = 5;\n\nfunction intervalMs(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_INTERVAL_MS'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INTERVAL_MS;\n}\n\nfunction verifyMismatchThreshold(): number {\n const raw = Number.parseInt(process.env['AGT_CLAUDE_UPDATE_VERIFY_THRESHOLD'] ?? '', 10);\n return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_VERIFY_MISMATCH_THRESHOLD;\n}\n\n/** Extract a version string (leading semver) from `claude --version` / npm output. */\nexport function parseVersion(output: string): string | null {\n const m = output.match(/\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*/);\n return m ? m[0] : null;\n}\n\n/** A target spec is \"concrete\" when it already names an exact version. */\nexport function isConcreteVersion(spec: string): boolean {\n return /^\\d+\\.\\d+\\.\\d+[A-Za-z0-9.+-]*$/.test(spec.trim());\n}\n\n/**\n * Compare two concrete versions by their numeric major.minor.patch core.\n * A prerelease (`1.2.3-beta.1`) sorts BEFORE its release (`1.2.3`), as in\n * semver. Returns null when either side does not parse, so callers can fail\n * closed rather than guess.\n */\nexport function compareVersions(a: string, b: string): number | null {\n const re = /^(\\d+)\\.(\\d+)\\.(\\d+)(-[0-9A-Za-z.-]+)?/;\n const ma = re.exec(a.trim());\n const mb = re.exec(b.trim());\n if (!ma || !mb) return null;\n for (let i = 1; i <= 3; i++) {\n const d = Number(ma[i]) - Number(mb[i]);\n if (d !== 0) return d < 0 ? -1 : 1;\n }\n const preA = ma[4] !== undefined;\n const preB = mb[4] !== undefined;\n if (preA === preB) return 0;\n return preA ? -1 : 1;\n}\n\n/**\n * Whether the manager should call the updater on this poll (ENG-11641\n * follow-up). Normally only inside the maintenance window; a host whose\n * installed Claude Code is below the security floor runs it regardless of the\n * window. No server target means \"change nothing\" (ENG-10620).\n */\nexport function shouldRunClaudeCodeUpdater(input: {\n desiredVersion: string | null | undefined;\n installedVersion: string | null | undefined;\n windowGate: 'proceed' | 'defer';\n floor?: string;\n}): boolean {\n if (!input.desiredVersion) return false;\n if (isBelowSecurityFloor(input.installedVersion, input.floor)) return true;\n return input.windowGate === 'proceed';\n}\n\n/**\n * True when `version` is a known version strictly below\n * {@link CLAUDE_CODE_SECURITY_FLOOR}. Unknown/unparseable → false: we only\n * escalate past the normal gates on a measured version, never on a guess.\n */\nexport function isBelowSecurityFloor(\n version: string | null | undefined,\n floor: string = CLAUDE_CODE_SECURITY_FLOOR,\n): boolean {\n if (!version) return false;\n const parsed = parseVersion(version);\n if (!parsed) return false;\n const cmp = compareVersions(parsed, floor);\n return cmp !== null && cmp < 0;\n}\n\n/**\n * Well-known absolute paths where a `claude` binary may live on managed hosts.\n * Probed alongside the PATH-resolved binary so we can see all install slots,\n * even ones not on the manager systemd unit's PATH.\n */\nexport const WELL_KNOWN_CLAUDE_PATHS: readonly string[] = [\n '/usr/bin/claude', // Anthropic native autoupdater (Linux deb/rpm)\n '/usr/local/bin/claude', // npm global on macOS Intel + Linux without nvm\n '/opt/homebrew/bin/claude', // Homebrew on macOS Apple Silicon\n '/home/linuxbrew/.linuxbrew/bin/claude', // Linuxbrew (used on agt-aws-* hosts)\n];\n\nexport interface ClaudeCodeUpdaterDeps {\n /** Server-pinned target: a concrete version or an npm dist-tag like 'latest'. */\n desiredVersion: string;\n /** Currently-installed version, if the caller already knows it (e.g. the\n * manager's cachedFrameworkVersion). When omitted we run `claude --version`. */\n currentVersion?: string | null;\n log: (msg: string) => void;\n /** Override \"now\" for tests. */\n now?: () => number;\n /** Run a command; injected in tests. Defaults to execFile. */\n runCommand?: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>;\n /** Raw `ps -eo pid,ppid,args` snapshot; injected in tests. */\n psSnapshot?: () => string;\n /** Sync existence probe for binary detection; injected in tests. */\n fileExists?: (path: string) => boolean;\n /** Read ~/.claude.json contents (raw); returns null when absent. Injected in tests. */\n readClaudeJson?: (path: string) => string | null;\n /** Resolve the user's home directory; injected in tests. */\n homeDir?: () => string;\n /** Resolve symlinks to a real path (throws when absent); injected in tests. */\n realPath?: (path: string) => string;\n /** Security floor override; defaults to CLAUDE_CODE_SECURITY_FLOOR. Injected in tests. */\n securityFloor?: string;\n}\n\nexport type UpdaterOutcome =\n | { action: 'throttled' }\n | { action: 'up_to_date'; version: string }\n | { action: 'deferred_busy'; target: string }\n | { action: 'skipped_native_install'; target: string }\n | { action: 'updated'; from: string | null; to: string }\n | { action: 'failed'; reason: string };\n\n// ── Host-global state (module-level; one updater per host process) ──────────\nlet lastCheckAt = 0;\n/** Per-process one-shot guard for the multi-install warn line. */\nlet multiInstallWarned = false;\n/**\n * Sticky counter for consecutive `post-install verify mismatch` against the\n * same target version. Reset on success, on target change, or after the\n * threshold error has been emitted (so we emit it exactly once per stuck run).\n */\nlet verifyMismatchState: { target: string; count: number; alarmed: boolean } | null = null;\n/**\n * When a below-floor host first deferred because it was busy. The\n * FORCED_UPDATE_HARD_DEADLINE_MS clock runs from here; cleared once the host\n * is at or above the floor. Process-local on purpose: a manager restart\n * restarts the clock, which costs at most one more deadline period.\n */\nlet belowFloorBusySince: number | null = null;\n\n/** Test helper — reset module state. Not for production use. */\nexport function _resetClaudeCodeUpdaterState(): void {\n lastCheckAt = 0;\n multiInstallWarned = false;\n verifyMismatchState = null;\n belowFloorBusySince = null;\n}\n\nfunction defaultPsSnapshot(): string {\n return execFileSync('ps', ['-eo', 'pid,ppid,args'], { encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024 });\n}\n\nfunction defaultReadClaudeJson(path: string): string | null {\n try {\n return readFileSync(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/** True when at least one `claude --name agt-*` agent process is live. */\nexport function hostHasLiveAgent(psOutput: string): boolean {\n return parsePsRows(psOutput).some((r) => CLAUDE_AGENT_PROCESS_PATTERN.test(r.args));\n}\n\n/**\n * ENG-5798: returns true when ~/.claude.json reports `installMethod: \"native\"`,\n * meaning Anthropic's native autoupdater owns the on-disk binary. In that case\n * the brew/npm update path is actively harmful — it installs a second binary\n * the host's PATH never resolves to, and verify reads from yet a third.\n */\nexport function readsAsNativeInstall(raw: string | null): boolean {\n if (!raw) return false;\n try {\n const parsed = JSON.parse(raw) as { installMethod?: unknown };\n return parsed.installMethod === 'native';\n } catch {\n return false;\n }\n}\n\n/**\n * ENG-10620: true when `realPath` is a binary owned by Claude Code's native\n * installer, which keeps every version under `…/claude/versions/<semver>`\n * (e.g. `~/.local/share/claude/versions/2.1.274`, symlinked from\n * `~/.local/bin/claude`). An npm global resolves into\n * `…/node_modules/@anthropic-ai/claude-code/` instead.\n */\nexport function isNativeInstallPath(realPath: string): boolean {\n return /\\/claude\\/versions\\/\\d+\\.\\d+\\.\\d+[^/]*$/.test(realPath);\n}\n\n/**\n * ENG-10620: decide native ownership from the binary this host ACTUALLY runs,\n * not from `~/.claude.json`.\n *\n * `installMethod` is written by whichever `claude` last started, not by the\n * one the manager launches. On agt-aws-1 an operator shell's PATH resolved a\n * native `~/.local/bin/claude` while agents ran the npm global at\n * `/usr/bin/claude`; that one manual run flipped `installMethod` to `native`\n * and this updater stopped converging the host to its pin for good, while the\n * binary it would have updated was never native at all.\n *\n * Resolution mirrors how agent sessions pick their binary (resolveClaudeBinary:\n * CLAUDE_PATH, then PATH). The config key remains only as a fallback for a\n * host where no binary can be resolved.\n */\nasync function resolveNativeOwnership(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n realPath: (path: string) => string,\n claudeJson: string | null,\n): Promise<{ native: boolean; binary: string | null; configSaysNative: boolean }> {\n const configSaysNative = readsAsNativeInstall(claudeJson);\n let resolved: string | null = null;\n const override = process.env['CLAUDE_PATH'];\n if (override) {\n resolved = override;\n } else {\n try {\n const { stdout } = await run('which', ['claude']);\n resolved = stdout.trim().split('\\n')[0]?.trim() || null;\n } catch {\n resolved = null;\n }\n }\n if (resolved) {\n try {\n const real = realPath(resolved);\n return { native: isNativeInstallPath(real), binary: real, configSaysNative };\n } catch {\n /* unresolvable — fall back to the config key */\n }\n }\n return { native: configSaysNative, binary: null, configSaysNative };\n}\n\n/**\n * Resolve the absolute filesystem path that `npm install -g <pkg>` will write\n * the `claude` binary to. Returns null when `npm prefix -g` fails or is empty,\n * in which case the caller should fall back to PATH-based verify.\n *\n * We deliberately use `npm prefix -g` rather than `which claude` because the\n * latter resolves through PATH, which on agt-aws-* hosts can land on\n * /usr/bin/claude (the native autoupdater slot) — exactly the misalignment\n * ENG-5798 is fixing.\n */\nasync function resolveInstallTargetPath(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n): Promise<string | null> {\n try {\n const { stdout } = await run('npm', ['prefix', '-g']);\n const prefix = stdout.trim();\n if (!prefix) return null;\n return `${prefix}/bin/claude`;\n } catch {\n return null;\n }\n}\n\n/**\n * Enumerate every `claude` binary visible to the host: PATH-resolved + the\n * well-known absolute slots + `~/.claude/local/claude`. Returns one entry per\n * unique resolved path with the version each reports (or null if it failed to\n * execute). Used purely for the one-shot drift warn line.\n */\nexport async function probeClaudeInstalls(\n run: (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string }>,\n fileExists: (path: string) => boolean,\n home: string,\n): Promise<Array<{ path: string; version: string | null }>> {\n const candidates = new Set<string>(WELL_KNOWN_CLAUDE_PATHS);\n candidates.add(`${home}/.claude/local/claude`);\n // PATH-resolved. `which` is portable enough across the Linux + macOS hosts\n // we target; errors here just mean \"no claude on PATH\", which is fine.\n try {\n const { stdout } = await run('which', ['claude']);\n const p = stdout.trim().split('\\n')[0]?.trim();\n if (p) candidates.add(p);\n } catch {\n /* no-op */\n }\n\n const results: Array<{ path: string; version: string | null }> = [];\n for (const p of candidates) {\n if (!fileExists(p)) continue;\n let version: string | null = null;\n try {\n const { stdout } = await run(p, ['--version']);\n version = parseVersion(stdout);\n } catch {\n /* binary exists but failed to execute — record path with null version */\n }\n results.push({ path: p, version });\n }\n return results;\n}\n\n/**\n * Bring Claude Code to the desired version if the host is idle and not already\n * on target. Safe to call every manager poll — internally throttled. Returns an\n * outcome describing what happened (for logging/observability/tests).\n */\nexport async function maybeUpdateClaudeCode(deps: ClaudeCodeUpdaterDeps): Promise<UpdaterOutcome> {\n const now = deps.now ? deps.now() : Date.now();\n const run = deps.runCommand ?? ((cmd, args) => execFileAsync(cmd, args, { maxBuffer: 16 * 1024 * 1024 }));\n const ps = deps.psSnapshot ?? defaultPsSnapshot;\n const fileExists = deps.fileExists ?? existsSync;\n const readClaudeJson = deps.readClaudeJson ?? defaultReadClaudeJson;\n const realPath = deps.realPath ?? realpathSync;\n const home = (deps.homeDir ?? homedir)();\n const { log } = deps;\n const floor = deps.securityFloor ?? CLAUDE_CODE_SECURITY_FLOOR;\n let forcedUnderLiveAgents = false;\n\n // 1. Host-global throttle.\n if (now - lastCheckAt < intervalMs()) {\n return { action: 'throttled' };\n }\n lastCheckAt = now;\n\n try {\n const spec = (deps.desiredVersion || 'latest').trim();\n\n // 2. ENG-5798 multi-install drift detection. Emit one warn line per\n // process listing every claude binary + version we can see. This is\n // independent of the install decision below — even on hosts where we\n // proceed normally, surfacing drift is the difference between a 3-day\n // silent loop and a one-line lead.\n if (!multiInstallWarned) {\n const installs = await probeClaudeInstalls(run, fileExists, home);\n if (installs.length > 1) {\n const summary = installs\n .map((i) => `${i.path}=${i.version ?? 'unknown'}`)\n .join(', ');\n log(`[claude-code-updater] multiple claude installs detected: ${summary}`);\n }\n multiInstallWarned = true;\n }\n\n // 3. ENG-5798 native-install short-circuit. When Anthropic's native\n // autoupdater owns the host binary, the brew/npm update path is\n // actively harmful (it installs a second binary the manager's PATH\n // never resolves to, then verifies against yet a third). Defer.\n // ENG-10869: the path the AGENT actually reads, not the legacy one.\n // Once this host migrates, `~/.claude.json` is ARCHIVED, so a hardcoded\n // read here returns nothing forever and `configSaysNative` is permanently\n // false — silently re-opening the ENG-5798 harm this short-circuit exists\n // to prevent (brew/npm installing a second binary on a host Anthropic's\n // native autoupdater owns). The resolver falls back to the legacy file, so\n // an unmigrated host is unaffected.\n const claudeJsonPath = effectiveClaudeJsonPath(home);\n const ownership = await resolveNativeOwnership(run, realPath, readClaudeJson(claudeJsonPath));\n if (!ownership.native && ownership.configSaysNative) {\n log(\n `[claude-code-updater] ${claudeJsonPath} says installMethod=native but the binary this host runs (${ownership.binary}) is not a native install — ignoring it and converging to the pin`,\n );\n }\n if (ownership.native) {\n // One-shot info line per process — same throttle as the warn above, but\n // we re-use the throttle window to keep this from logging every poll.\n log('[claude-code-updater] native install detected, deferring to Anthropic autoupdater');\n // Reset any sticky verify-mismatch state — it can't apply once we stop\n // attempting installs.\n verifyMismatchState = null;\n return { action: 'skipped_native_install', target: spec };\n }\n\n // 4. Resolve the target to a concrete version (read-only). A concrete spec\n // is its own target; a dist-tag is resolved via `npm view`.\n let target: string;\n if (isConcreteVersion(spec)) {\n target = spec;\n } else {\n const { stdout } = await run('npm', ['view', `${NPM_PKG}@${spec}`, 'version']);\n const resolved = parseVersion(stdout);\n if (!resolved) {\n log(`[claude-code-updater] could not resolve ${NPM_PKG}@${spec} to a version; skipping`);\n return { action: 'failed', reason: 'unresolved_target' };\n }\n target = resolved;\n }\n\n // 4b. Security floor: never converge to a version below it. A pin (or a\n // stale fleet default) under the floor would otherwise short-circuit at\n // step 6 as \"up_to_date\" and keep the host vulnerable forever.\n const targetVsFloor = compareVersions(target, floor);\n if (targetVsFloor !== null && targetVsFloor < 0) {\n log(\n `[claude-code-updater] target ${target} is below the security floor ${floor} (ENG-11641: CVE-2026-111497 + emSign root) — raising the target to the floor`,\n );\n target = floor;\n }\n\n // 5. Determine the installed version (prefer the caller's cached value).\n let current = deps.currentVersion ?? null;\n if (!current) {\n try {\n const { stdout } = await run('claude', ['--version']);\n current = parseVersion(stdout);\n } catch {\n current = null; // claude missing/broken — treat as \"needs install\".\n }\n }\n\n const belowFloor = isBelowSecurityFloor(current, floor);\n if (!belowFloor) belowFloorBusySince = null;\n\n // 6. Already on target → nothing to do.\n if (current && current === target) {\n // Successful no-op — clear sticky verify mismatch state for this target.\n if (verifyMismatchState && verifyMismatchState.target === target) {\n verifyMismatchState = null;\n }\n return { action: 'up_to_date', version: target };\n }\n\n // 7. Idle gate — never replace the binary under a live agent session.\n // Exception: a host below the security floor defers for at most\n // FORCED_UPDATE_HARD_DEADLINE_MS, then installs under live agents if the\n // npm slot is provably the running binary (see the module header; the\n // same bounded-wait shape as the forced agt-cli update, ENG-8449).\n if (hostHasLiveAgent(ps())) {\n if (!belowFloor) {\n log(`[claude-code-updater] host busy (live agent) — deferring update to ${target} to a later cycle`);\n return { action: 'deferred_busy', target };\n }\n if (belowFloorBusySince === null) belowFloorBusySince = now;\n const waitedMs = now - belowFloorBusySince;\n if (waitedMs < FORCED_UPDATE_HARD_DEADLINE_MS) {\n // Re-check in ~1 min instead of the normal 6h throttle, so the deadline\n // is actually observed when it passes.\n lastCheckAt = now - intervalMs() + BELOW_FLOOR_RECHECK_MS;\n log(\n `[claude-code-updater] host busy (live agent) and ${current} is below the security floor ${floor} — deferring update to ${target}; will force it in ${Math.ceil((FORCED_UPDATE_HARD_DEADLINE_MS - waitedMs) / 60_000)}m if the npm install slot is the binary agents run`,\n );\n return { action: 'deferred_busy', target };\n }\n // Review 97810715f303 / ENG-5798: before overriding the idle gate, check\n // the slot npm actually writes. If it is ALREADY at or above the floor,\n // the below-floor reading came from a different binary on PATH (the\n // multi-install case), so an install here cannot fix anything. Forcing it\n // would re-run `npm install -g` under live agents every cycle while the\n // PATH binary stays put. Fall back to the normal idle-gated behaviour and\n // say why.\n const slotPath = await resolveInstallTargetPath(run);\n let slotVersion: string | null = null;\n if (slotPath) {\n try {\n slotVersion = parseVersion((await run(slotPath, ['--version'])).stdout);\n } catch {\n slotVersion = null;\n }\n }\n // Review fe4300a0793c: an unresolvable slot means we cannot prove an\n // install would help either, so it gets the same fall-back.\n if (!slotPath) {\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] ERROR security_floor_blocked: deadline reached but \\`npm prefix -g\\` did not resolve — cannot prove an install would reach the binary agents run; not forcing an install under live agents`,\n );\n return { action: 'deferred_busy', target };\n }\n // Review 0a3b967ea569: only force when there is positive evidence that\n // the slot npm writes IS the binary agents run and IS below the floor:\n // its version is readable, below the floor, and equal to what PATH\n // reports. An empty/broken slot (version unreadable), a slot already at\n // or above the floor, or a slot on a different version than PATH all mean\n // an install cannot be shown to help, so fall back to the idle gate.\n const slotIsTheRunningBinary =\n slotVersion !== null && isBelowSecurityFloor(slotVersion, floor) && slotVersion === current;\n if (!slotIsTheRunningBinary) {\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] ERROR security_floor_blocked: PATH claude reports ${current} (below the security floor ${floor}) but the npm install slot ${slotPath} reports ${slotVersion ?? 'no readable version'} — cannot show the install would reach the binary agents run (multi-install, ENG-5798); not forcing an install under live agents, investigate the host's claude install slots`,\n );\n return { action: 'deferred_busy', target };\n }\n log(\n `[claude-code-updater] security floor deadline reached: ${current} < ${floor} and host still busy after ${Math.round(waitedMs / 60_000)}m — installing ${target} anyway (ENG-11641)`,\n );\n forcedUnderLiveAgents = true;\n }\n\n // 8. Install the target, then verify against the *absolute* path of the\n // binary npm just wrote — not via PATH. PATH resolution is the original\n // ENG-5798 bug: on hosts with multiple claude installs, PATH lands on a\n // different binary than the one npm overwrites, so verify never agrees.\n const installTargetPath = await resolveInstallTargetPath(run);\n log(`[claude-code-updater] updating Claude Code ${current ?? '(none)'} → ${target}`);\n // ENG-11669 (Obi Wan d623befb7109): hold the package-cache lock so the\n // disk guard's `npm cache clean` can never run under this install.\n await withPackageCacheLock(() => run('npm', ['install', '-g', `${NPM_PKG}@${target}`]), {\n label: 'the Claude Code install',\n log,\n });\n\n // Prefer the absolute install target; fall back to PATH only when\n // `npm prefix -g` failed (in which case verify is best-effort).\n const verifyTarget = installTargetPath ?? 'claude';\n let installed: string | null = null;\n try {\n const { stdout } = await run(verifyTarget, ['--version']);\n installed = parseVersion(stdout);\n } catch {\n installed = null;\n }\n\n if (installed !== target) {\n // Review fe4300a0793c: a forced install that did not verify must not keep\n // the old deadline, or every later cycle would force again immediately.\n belowFloorBusySince = null;\n log(\n `[claude-code-updater] post-install verify mismatch: expected ${target}, got ${installed ?? 'unknown'} from ${verifyTarget}`,\n );\n\n // ENG-5798: hard-error after N consecutive mismatches against the same\n // target. Reset the counter when the target changes — a new pin is a\n // new event, not a continuation of the old stuck run.\n if (!verifyMismatchState || verifyMismatchState.target !== target) {\n verifyMismatchState = { target, count: 1, alarmed: false };\n } else {\n verifyMismatchState.count += 1;\n }\n\n const threshold = verifyMismatchThreshold();\n if (verifyMismatchState.count >= threshold && !verifyMismatchState.alarmed) {\n verifyMismatchState.alarmed = true;\n log(\n `[claude-code-updater] ERROR verify_mismatch_stuck: ${verifyMismatchState.count} consecutive verify failures for target ${target} (verify path: ${verifyTarget}) — install is not taking effect, investigate host install slots`,\n );\n return { action: 'failed', reason: 'verify_mismatch_threshold' };\n }\n return { action: 'failed', reason: 'verify_mismatch' };\n }\n\n // Successful install — clear sticky state. Also end any security-floor wait\n // episode, so a later below-floor reading starts a fresh deadline instead of\n // inheriting this one's (review 97810715f303).\n verifyMismatchState = null;\n belowFloorBusySince = null;\n log(`[claude-code-updater] updated Claude Code ${current ?? '(none)'} → ${installed}`);\n if (forcedUnderLiveAgents) {\n // Review 8f1c08ba3c77: from here `claude --version` (and so the heartbeat's\n // framework_version) reads the new version, but the live sessions that held\n // the idle gate still execute the OLD binary until they restart. Say so\n // loudly, so the host does not read as remediated when it is not. The\n // recycle of those sessions is runtime-image-recycle's job (#7134, host\n // mode: /proc/<pid>/exe reads \"(deleted)\").\n log(\n `[claude-code-updater] WARNING installed ${installed} under live agents: running sessions keep the previous ${current ?? 'unknown'} binary until they restart. framework_version now reports ${installed}, which does NOT mean the live sessions are patched`,\n );\n }\n return { action: 'updated', from: current, to: installed };\n } catch (err) {\n // Review f540008e5f47: a forced install that THREW (npm non-zero exit,\n // EACCES, registry/network error) is the commonest failed install. Like a\n // failed verify, it must not keep the old deadline.\n if (forcedUnderLiveAgents) belowFloorBusySince = null;\n // Log-and-continue: an updater failure must never break the manager tick.\n log(`[claude-code-updater] update attempt failed: ${err instanceof Error ? err.message : String(err)}`);\n return { action: 'failed', reason: 'exception' };\n }\n}\n","/**\n * ENG-11669 — keep a host's root disk from filling up, and never start a CLI\n * self-update into a full disk.\n *\n * What happened (alyve-host-1, 23 Oct 14:00:40Z): the root disk was full when\n * the manager's self-update ran `npm install -g @integrity-labs/agt-cli@…`.\n * npm removes the old package before writing the new one, the write died with\n * ENOSPC, and `/usr/bin/agt` was simply gone. The manager could not start again,\n * and six agents were dark for ~33h until a human freed disk over SSM. The\n * ENG-6698 smoke check could not help: it runs AFTER the install, and by then\n * the old binary was already deleted.\n *\n * Two defences, both here:\n * 1. `ensureDiskForSelfUpdate` - before any self-update install, make sure the\n * disk has room. If it does not, run the safe cleanup once and re-check. If\n * it still does not, refuse the install (keep the working binary) and say so.\n * 2. `maybeRunDiskGuard` - hourly, and independent of any update: when the disk\n * is low, run the same safe cleanup, so a slow fill (package caches, journal)\n * is reclaimed long before anything ENOSPCs.\n *\n * SAFE CLEANUP is deliberately narrow. Every step reclaims a CACHE or a log that\n * the system rebuilds on demand, and none of them can touch agent work:\n * - `journalctl --vacuum-size=200M` (systemd journal)\n * - `npm cache clean --force` (npm's content cache)\n * - `pnpm store prune` (unreferenced packages only, by design)\n * It never deletes files by path. In particular it never touches\n * `/root/.claude/projects` (agent transcripts, the ONLY copy of the last 7 days\n * on a host that missed its log-retention reconcile) or agent workspaces.\n *\n * Pure decision functions plus injected effects, so every branch is testable\n * without a real disk or a real `sudo`.\n */\n\nimport { readdirSync, readFileSync, statSync, statfsSync } from 'node:fs';\nimport { execFileSync } from 'node:child_process';\n\nconst GIB = 1024 * 1024 * 1024;\n\n/** Free/total bytes on a filesystem, as seen by an unprivileged writer. */\nexport interface DiskStat {\n freeBytes: number;\n totalBytes: number;\n}\n\n/**\n * The disk is LOW below one threshold: 10% of the volume, clamped to\n * [2 GiB, 5 GiB]. Small disks get the 2 GiB floor (on 8 GB, 10% would be too\n * tight to be useful); big disks get the 5 GiB cap, so a 200 GB volume with\n * 15 GiB free is NOT low (Obi Wan 97b2c0d967a7: OR-ing the two bounds made a\n * big, moderately full disk 'low' forever, vacuuming the journal every hour\n * and raising a false STILL LOW, with no ENOSPC risk behind it).\n */\nexport const DISK_LOW_FREE_RATIO = 0.1;\nexport const DISK_LOW_FREE_BYTES = 2 * GIB;\nexport const DISK_LOW_FREE_CAP_BYTES = 5 * GIB;\n\nexport function diskLowThresholdBytes(totalBytes: number): number {\n return Math.min(DISK_LOW_FREE_CAP_BYTES, Math.max(DISK_LOW_FREE_BYTES, totalBytes * DISK_LOW_FREE_RATIO));\n}\n\n/**\n * Minimum free space to START a self-update install. The agt-cli package plus\n * npm's staging is a few hundred MB; 1 GiB leaves room for the install AND for\n * the agents still writing while it runs.\n */\nexport const SELF_UPDATE_MIN_FREE_BYTES = 1 * GIB;\n\n/** The hourly guard's throttle. */\nexport const DISK_GUARD_INTERVAL_MS = 60 * 60 * 1000;\n\n/** Read free/total for `path`. `null` when the stat itself fails (reported, never guessed). */\nexport function readDiskStat(path = '/', statfs: typeof statfsSync = statfsSync): DiskStat | null {\n try {\n const s = statfs(path);\n // bavail: blocks available to a NON-root writer. The manager may run as\n // root, but agents and npm's cache writers often do not, and root's reserved\n // blocks are exactly what lets a \"full\" disk limp on; measuring without them\n // is the honest number.\n const bsize = Number(s.bsize);\n return { freeBytes: Number(s.bavail) * bsize, totalBytes: Number(s.blocks) * bsize };\n } catch {\n return null;\n }\n}\n\nexport type DiskVerdict = 'ok' | 'low' | 'unknown';\n\nexport function diskVerdict(stat: DiskStat | null): DiskVerdict {\n if (!stat || !Number.isFinite(stat.freeBytes) || !Number.isFinite(stat.totalBytes) || stat.totalBytes <= 0) {\n return 'unknown';\n }\n return stat.freeBytes < diskLowThresholdBytes(stat.totalBytes) ? 'low' : 'ok';\n}\n\n/** One cleanup step: a binary and its args. Never a shell string, never a path to delete. */\nexport interface CleanupStep {\n bin: string;\n args: string[];\n /**\n * Needs root (the system journal). The npm cache and pnpm store are the\n * MANAGER USER's own, so those run as that user, never under sudo: under\n * sudo, HOME can become /root and the command would clean root's cache\n * instead of the one that is actually growing (Obi Wan c33094e187b2).\n */\n needsRoot: boolean;\n /**\n * Touches a package-manager cache that a concurrent install reads from.\n * Skipped while any npm/pnpm install is running (Obi Wan b8ed1339eaf2):\n * wiping the cache under `npm install -g @anthropic-ai/claude-code` (the\n * Claude Code updater runs in the same poll) could fail that install after\n * npm already removed the old package, the missing-binary failure again.\n */\n packageCache: boolean;\n}\n\nexport const SAFE_CLEANUP_STEPS: readonly CleanupStep[] = [\n { bin: 'journalctl', args: ['--vacuum-size=200M'], needsRoot: true, packageCache: false },\n { bin: 'npm', args: ['cache', 'clean', '--force'], needsRoot: false, packageCache: true },\n { bin: 'pnpm', args: ['store', 'prune'], needsRoot: false, packageCache: true },\n];\n\n/**\n * Obi Wan 3c3bd567504f: on a NON-root manager the self-update runs\n * `sudo -n npm install -g`, whose cache is root's when sudo resets HOME. So a\n * non-root manager cleans BOTH: its own cache (above, no sudo) and root's\n * (`sudo -n -H`, HOME forced to root's). On a root manager they are the same\n * cache and this step is not run.\n */\nexport const NON_ROOT_EXTRA_STEPS: readonly CleanupStep[] = [\n { bin: 'npm', args: ['cache', 'clean', '--force'], needsRoot: true, packageCache: true },\n];\n\n/** Async on purpose: the guard runs inside the manager's poll loop, and a synchronous\n * `npm cache clean` would freeze heartbeats for as long as it takes. */\nexport type ExecFn = (cmd: string, args: string[], opts: { timeout: number }) => Promise<void>;\n\nexport interface CleanupStepResult {\n step: string;\n outcome: 'ran' | 'missing' | 'failed' | 'skipped';\n detail?: string;\n}\n\n/**\n * Run every safe cleanup step, best-effort and independently: one failing (or\n * not installed, e.g. pnpm on a host that never used it) never stops the rest.\n * Elevates via `sudo -n` when not root, exactly like the npm self-update path.\n */\nexport async function runSafeCleanup(deps: {\n exec: ExecFn;\n isRoot: boolean;\n timeoutMs?: number;\n /**\n * Is an npm/pnpm install running right now? true, or a check that could not\n * answer, SKIPS the package-cache steps (fail safe: a skipped clean costs\n * one hour of cache; a clean under an install can cost the binary).\n */\n packageManagerBusy?: () => Promise<boolean>;\n}): Promise<CleanupStepResult[]> {\n const timeout = deps.timeoutMs ?? CLEANUP_STEP_TIMEOUT_MS;\n const results: CleanupStepResult[] = [];\n const steps = deps.isRoot ? SAFE_CLEANUP_STEPS : [...SAFE_CLEANUP_STEPS, ...NON_ROOT_EXTRA_STEPS];\n for (const step of steps) {\n const label = `${step.needsRoot && !deps.isRoot ? 'sudo ' : ''}${step.bin} ${step.args.join(' ')}`;\n if (step.packageCache) {\n // Re-checked before EVERY package-cache step (Obi Wan d623befb7109): a\n // step can take minutes, and an install started outside this process is\n // not held off by the lock.\n const busy = deps.packageManagerBusy ? await deps.packageManagerBusy().catch(() => true) : false;\n if (busy) {\n results.push({ step: label, outcome: 'skipped', detail: 'an npm/pnpm install is running' });\n continue;\n }\n }\n const [cmd, args] =\n deps.isRoot || !step.needsRoot\n ? [step.bin, step.args]\n : ['sudo', [...(step.bin === 'journalctl' ? ['-n'] : ['-n', '-H']), step.bin, ...step.args]];\n try {\n await deps.exec(cmd, args, { timeout });\n results.push({ step: label, outcome: 'ran' });\n } catch (err) {\n const e = err as NodeJS.ErrnoException & { status?: number; stderr?: Buffer | string };\n const stderr = e.stderr ? String(e.stderr) : '';\n // ENOENT (no such binary) as root, or sudo's \"command not found\" as non-root.\n const missing = e.code === 'ENOENT' || /command not found|not found/i.test(stderr);\n results.push({\n step: label,\n outcome: missing ? 'missing' : 'failed',\n detail: (stderr || e.message || '').trim().slice(0, 200),\n });\n }\n }\n return results;\n}\n\nfunction mb(bytes: number): string {\n return `${Math.round(bytes / (1024 * 1024))}MB`;\n}\n\nfunction summarize(results: CleanupStepResult[]): string {\n return results.map((r) => `${r.step}=${r.outcome}`).join(', ');\n}\n\nexport interface DiskGuardDeps {\n readStat: () => DiskStat | null;\n cleanup: () => Promise<CleanupStepResult[]>;\n log: (line: string) => void;\n}\n\nexport type SelfUpdateRoom =\n /**\n * `release` MUST be called once the install has finished (or failed): the\n * self-update holds the package-cache lock from its checks through its install,\n * so nothing can start a cleanup or another npm install in between\n * (Obi Wan a2c6f266bf18).\n */\n | { ok: true; freeBytes: number | null; cleaned: boolean; release: () => void }\n | { ok: false; freeBytes: number; cleaned: true }\n /** ENG-11669 (Obi Wan 84656ed7dfce): held because the package cache stayed busy; disk not measured. */\n | { ok: false; freeBytes: null; cleaned: false; reason: 'package_cache_busy' };\n\n/**\n * ENG-11669: is there room to START a self-update install? Called immediately\n * before the install, the only point at which the answer is current.\n *\n * - Enough room: go.\n * - Not enough: run the safe cleanup once, re-measure, go if it worked.\n * - Still not enough: REFUSE. The caller keeps the working binary and retries\n * on a later cycle; a refused update costs a version, an ENOSPC mid-install\n * costs the host.\n * - Unreadable stat: go, as before this guard existed. Failing closed on an\n * unreadable statfs would freeze every update on a host with an odd mount,\n * which is a worse outcome than the one this guards against; it is logged.\n */\n/**\n * ENG-11669 (Obi Wan 7c1384a483ca): hold-episode state. While a host is held,\n * the self-update check still runs every few minutes; without this every one of\n * them would re-run a cleanup that already failed and re-log the HELD line,\n * roughly 288 times a day into a disk that is already full. Same shape as the\n * ENG-10724 device-state hold's latch: one HELD line per episode, cleanup\n * retried at most once per DISK_GUARD_INTERVAL_MS, both reset the moment there\n * is room again.\n */\nlet holdEpisode: { lastCleanupMs: number } | null = null;\nlet busyEpisode: { since: number; loggedAt: number } | null = null;\nexport function resetSelfUpdateHoldEpisode(): void {\n holdEpisode = null;\n busyEpisode = null;\n forcedHoldAnnounced = false;\n forcedBusyAnnounced = false;\n}\n\n/**\n * Obi Wan 39885fe919e4: a held forced \"Update CLI now\" is re-kicked every\n * update check (~5 min) until it succeeds, each time with force=true, so the\n * forced HELD line is latched like the device-state hold's: said once per\n * forced REQUEST, re-armed by the caller when a new request arrives\n * (rearmForcedDiskHoldAnnounce, wired next to selfUpdateForcedHoldAnnounced).\n */\nlet forcedHoldAnnounced = false;\n/** Obi Wan 253abefc6771: a forced request held on a BUSY package cache is told why, once per request. */\nlet forcedBusyAnnounced = false;\nexport function rearmForcedDiskHoldAnnounce(): void {\n forcedHoldAnnounced = false;\n forcedBusyAnnounced = false;\n}\n\n/**\n * One lock around everything that reads or wipes the package caches: the safe\n * cleanup, and package installs (the Claude Code updater's `npm install -g`).\n * Obi Wan 7a21830b25c1 / d623befb7109: the hourly guard runs fire-and-forget\n * and an async install can start while it runs, so a single up-front \"is an\n * install running?\" check is not enough. Holders run strictly one at a time,\n * in arrival order. The agt self-update check waits for the lock to go idle\n * before it measures and returns; its install is synchronous (execFileSync\n * blocks the event loop), so nothing can start a cleanup under it.\n */\nlet cacheLockTail: Promise<void> = Promise.resolve();\nlet cacheLockHolders = 0;\n\n/**\n * Obi Wan 84656ed7dfce: no holder may own the lock forever. A stalled\n * `npm install -g` (a registry hang) must not park the agt self-update, the\n * host's remediation channel, behind it indefinitely. After this long the lock\n * is RELEASED (logged) even if the holder is still running; the holder itself\n * is not killed, and anything queued may then proceed. Both remaining sides\n * re-check with pgrep: the cleanup before each package-cache step, and the agt\n * self-update before it starts its install (Obi Wan 59205b9de2aa).\n */\nexport const PACKAGE_CACHE_LOCK_MAX_HOLD_MS = 10 * 60 * 1000;\n/** How long the agt self-update check waits for the lock before holding instead. */\n// Obi Wan 5e64cc74a59a / 128538f49206: the self-update's bounded wait to\n// ACQUIRE the lock. See SELF_UPDATE_LOCK_MAX_HOLD_MS for the full budget.\nexport const PACKAGE_CACHE_IDLE_MAX_WAIT_MS = 5 * 60 * 1000;\n/** Per-step timeout for the safe cleanup. */\nexport const CLEANUP_STEP_TIMEOUT_MS = 120_000;\n/**\n * The self-update holds the lock across measure -> cleanup -> install. Worst\n * case inside the hold: cleanup of 4 steps x 120s (non-root) = 8 min, plus the\n * install (npm 180s timeout) = 11 min, so a 12 min max hold.\n *\n * Against the 20 min self-update latch (Obi Wan 128538f49206), which is taken\n * BEFORE the pre-install network checks: brew update + outdated (60s + 30s)\n * = 1.5 min, then the 5 min acquire bound, then the 12 min hold, then the 15s\n * smoke check = 18.75 min worst case. Inside 20.\n */\nexport const SELF_UPDATE_LOCK_MAX_HOLD_MS = 12 * 60 * 1000;\n/**\n * The self-update's own command timeouts, shared with manager-worker.ts so the\n * latch budget test reads the values that actually run (Obi Wan 6909f9596353).\n */\nexport const SELF_UPDATE_TIMEOUTS = {\n brewUpdateMs: 60_000,\n brewOutdatedMs: 30_000,\n brewUpgradeMs: 120_000,\n npmInstallMs: 180_000,\n smokeCheckMs: 15_000,\n} as const;\n/**\n * Obi Wan 51f6036d7808: the longest the self-update defers to \"another install\n * is running\". Past it the install goes ahead (logged): a looping, hung or\n * hostile install must not hold the host's remediation channel forever. Ours is\n * still serialised by the package-cache lock; this only stops deferring to\n * processes outside it.\n */\nexport const SELF_UPDATE_BUSY_CEILING_MS = 2 * 60 * 60 * 1000;\n/** Busy holds are logged once, then re-said (as a warning) at most this often. */\nexport const BUSY_HOLD_RELOG_MS = 60 * 60 * 1000;\n\nconst INSTALL_VERBS = new Set(['install', 'i', 'ci', 'add', 'update', 'upgrade', 'up']);\nconst NOT_AN_INSTALL = new Set(['run', 'run-script', 'exec', 'x', 'dlx', 'test', 'start']);\nconst PM_BINS = /^(npm|pnpm)$/;\nconst PM_SCRIPTS = /^(npm|npm-cli\\.js|pnpm|pnpm\\.c?js)$/;\nconst basenameOf = (p: string) => p.slice(p.lastIndexOf('/') + 1);\n\n/**\n * Obi Wan 106f4019f3b9: is THIS process a package-manager install? Only when\n * the process itself is npm/pnpm (argv[0], or node running the npm/pnpm\n * script) and its first subcommand is an install verb. A shell whose command\n * line merely mentions one (`bash -c \"npm install && npm run dev\"`, alive for\n * the life of the dev server) is the shell, not an install, and does not count;\n * its npm child does, for exactly as long as that child runs.\n */\nexport function isPackageInstallArgv(argv: readonly string[]): boolean {\n let args = argv.filter((a) => a.length > 0);\n // npm overwrites its process title (\"npm install\"), which reads back as one\n // space-joined argv entry.\n if (args.length === 1 && args[0]!.includes(' ')) args = args[0]!.split(/\\s+/);\n if (args.length === 0) return false;\n let rest: string[];\n const bin = basenameOf(args[0]!);\n if (PM_BINS.test(bin)) rest = args.slice(1);\n else if (/^node(js)?$/.test(bin) && args[1] && PM_SCRIPTS.test(basenameOf(args[1]))) rest = args.slice(2);\n else return false;\n const sub = rest.find((a) => !a.startsWith('-'));\n if (!sub || NOT_AN_INSTALL.has(sub)) return false;\n if (INSTALL_VERBS.has(sub)) return true;\n // A flag value before the subcommand (`npm --prefix /x install`): look one further.\n const next = rest.slice(rest.indexOf(sub) + 1).find((a) => !a.startsWith('-'));\n return next !== undefined && INSTALL_VERBS.has(next);\n}\n\n/**\n * Host-wide: is any npm/pnpm install running that could collide with ours?\n *\n * - Linux: read /proc. A process whose ROOT FILESYSTEM is not ours (an agent's\n * container) is skipped: its npm cache and global prefix live on a different\n * filesystem, so it cannot race our cleanup or our `npm install -g` (Obi Wan\n * e43cffc301c5 / 51f6036d7808). Identity is the dev:ino of /proc/<pid>/root,\n * NOT the mount namespace (Obi Wan ee7fd03f9843): a systemd unit with\n * PrivateTmp/ProtectSystem, a snap or an SSM session can sit in its own\n * mount namespace while sharing the host's root, and those still count. A\n * root we cannot stat is counted (fail safe); a pid that vanished mid-scan is\n * skipped. Statting another uid's /proc/<pid>/root needs ptrace access: the\n * fleet's manager runs as root (agt-manager.service, User=root by default),\n * so it can. A non-root manager would count other uids' container installs\n * as busy, which costs at most SELF_UPDATE_BUSY_CEILING_MS of deferral (Obi\n * Wan 7e45e4c1a576).\n * - No /proc (macOS, Obi Wan eff7b7bd11f3): fall back to `ps -A -o args=`, the\n * space-joined argv of every process. Install argv has no spaces inside its\n * tokens in practice, so splitting on whitespace is good enough here.\n * - Neither readable: throws, which the callers read as busy. That is bounded:\n * the self-update stops deferring after SELF_UPDATE_BUSY_CEILING_MS.\n */\nexport function anyPackageInstallRunning(\n deps: {\n listPids?: () => string[];\n readCmdline?: (pid: string) => string | null;\n /** dev:ino of the process's root directory; null when it cannot be read. */\n readRootId?: (pid: string) => string | null;\n listProcessArgs?: () => string[];\n selfPid?: number;\n } = {},\n): boolean {\n let pids: string[] | null;\n try {\n pids = (deps.listPids ?? (() => readdirSync('/proc').filter((d) => /^\\d+$/.test(d))))();\n } catch (err) {\n if ((err as { code?: string }).code !== 'ENOENT') throw err;\n pids = null; // no /proc at all\n }\n if (pids === null) {\n const lines = (deps.listProcessArgs ?? defaultPsArgs)();\n return lines.some((line) => isPackageInstallArgv(line.trim().split(/\\s+/)));\n }\n const readCmdline =\n deps.readCmdline ??\n ((pid: string) => {\n try {\n return readFileSync(`/proc/${pid}/cmdline`, 'utf8');\n } catch {\n return null;\n }\n });\n const readRootId =\n deps.readRootId ??\n ((pid: string) => {\n try {\n const st = statSync(`/proc/${pid}/root/`);\n return `${st.dev}:${st.ino}`;\n } catch {\n return null;\n }\n });\n const self = String(deps.selfPid ?? process.pid);\n const selfRoot = readRootId('self');\n for (const pid of pids) {\n if (pid === self) continue;\n const raw = readCmdline(pid);\n if (!raw || !isPackageInstallArgv(raw.split('\\0'))) continue;\n const root = readRootId(pid);\n if (selfRoot && root && root !== selfRoot) continue; // another container's root filesystem\n return true;\n }\n return false;\n}\n\nfunction defaultPsArgs(): string[] {\n return execFileSync('ps', ['-A', '-o', 'args='], { encoding: 'utf8', timeout: 5_000 }).split('\\n').filter(Boolean);\n}\n\n/**\n * Acquire the package-cache lock, waiting at most `maxWaitMs` (default: no\n * limit). Returns a release function, or null when the wait bound is hit. A\n * waiter that gives up keeps its queue slot but releases it the instant it is\n * reached, so it never blocks anyone behind it. Every acquisition is\n * force-released after `maxHoldMs` (logged) so no holder owns it forever.\n */\nexport async function acquirePackageCacheLock(\n opts: { maxWaitMs?: number; maxHoldMs?: number; label?: string; log?: (line: string) => void } = {},\n): Promise<(() => void) | null> {\n const prev = cacheLockTail;\n let releaseTail!: () => void;\n cacheLockTail = new Promise<void>((r) => (releaseTail = r));\n\n let abandoned = false;\n let waitTimer: ReturnType<typeof setTimeout> | undefined;\n const acquired = await new Promise<boolean>((resolve) => {\n void prev.then(() => {\n if (abandoned) {\n releaseTail(); // reached our slot after giving up: pass it straight on\n return;\n }\n if (waitTimer) clearTimeout(waitTimer);\n resolve(true);\n });\n if (opts.maxWaitMs !== undefined) {\n waitTimer = setTimeout(() => {\n abandoned = true;\n resolve(false);\n }, opts.maxWaitMs);\n (waitTimer as { unref?: () => void }).unref?.();\n }\n });\n if (!acquired) return null;\n\n cacheLockHolders += 1;\n let released = false;\n const maxHold = opts.maxHoldMs ?? PACKAGE_CACHE_LOCK_MAX_HOLD_MS;\n const releaseOnce = () => {\n if (released) return;\n released = true;\n clearTimeout(holdTimer);\n cacheLockHolders -= 1;\n releaseTail();\n };\n const holdTimer = setTimeout(() => {\n (opts.log ?? console.warn)(\n `[disk-guard] package-cache lock held ${Math.round(maxHold / 60_000)}m by ${opts.label ?? 'a holder'} that is still running; ` +\n 'releasing it so a stalled install cannot block the self-update or cleanup forever [ENG-11669]',\n );\n releaseOnce();\n }, maxHold);\n (holdTimer as { unref?: () => void }).unref?.();\n return releaseOnce;\n}\n\nexport async function withPackageCacheLock<T>(\n fn: () => Promise<T>,\n opts: { maxHoldMs?: number; label?: string; log?: (line: string) => void } = {},\n): Promise<T> {\n const release = (await acquirePackageCacheLock(opts))!;\n try {\n return await fn();\n } finally {\n release();\n }\n}\nasync function exclusiveCleanup(cleanup: () => Promise<CleanupStepResult[]>): Promise<CleanupStepResult[]> {\n return withPackageCacheLock(cleanup, { label: 'the disk-guard cleanup' });\n}\n/** Test seam: drop all lock state (a test that leaks a holder must not wedge the next). */\nexport function resetPackageCacheLock(): void {\n cacheLockTail = Promise.resolve();\n cacheLockHolders = 0;\n}\n/**\n * Is anything holding the package-cache lock? A cleanup, a toolkit/updater\n * install, or the agt self-update between its disk check and its install.\n */\nexport function cleanupIsRunning(): boolean {\n return cacheLockHolders > 0;\n}\n\n/**\n * ENG-11669: is there room to START a self-update install? Called immediately\n * before the install, the only point at which the answer is current.\n *\n * - Enough room: go.\n * - Not enough: run the safe cleanup once, re-measure, go if it worked.\n * - Still not enough: REFUSE. The caller keeps the working binary and retries\n * on a later cycle; a refused update costs a version, an ENOSPC mid-install\n * costs the host. Within a hold episode the refusal is quiet and the cleanup\n * is not re-run more than hourly.\n * - Unreadable stat: go, as before this guard existed. Failing closed on an\n * unreadable statfs would freeze every update on a host with an odd mount,\n * which is a worse outcome than the one this guards against; it is logged.\n */\nexport async function ensureDiskForSelfUpdate(\n deps: DiskGuardDeps & {\n nowMs?: number;\n force?: boolean;\n idleWaitMs?: number;\n /**\n * Obi Wan 59205b9de2aa: any npm/pnpm install running on the host, whether or\n * not it took our lock (a force-released stalled one, or someone else's).\n * true, or a check that cannot answer, HOLDS the self-update.\n */\n packageManagerBusy?: () => Promise<boolean>;\n },\n): Promise<SelfUpdateRoom> {\n const busyHold = (why: 'lock' | 'pgrep'): SelfUpdateRoom => {\n // Obi Wan 106f4019f3b9: latched like the disk HELD line. Said once when the\n // busy episode starts, then at most hourly, as an escalation naming how long.\n const now = deps.nowMs ?? Date.now();\n let first = false;\n if (busyEpisode === null) {\n busyEpisode = { since: now, loggedAt: now };\n first = true;\n }\n // A forced \"Update CLI now\" has a human waiting on the answer: say why\n // once per request even inside a latched episode, like the disk hold does.\n const announceForced = deps.force === true && !forcedBusyAnnounced;\n if (announceForced) forcedBusyAnnounced = true;\n if (first || announceForced || now - busyEpisode.loggedAt >= BUSY_HOLD_RELOG_MS) {\n const heldMin = Math.round((now - busyEpisode.since) / 60_000);\n busyEpisode.loggedAt = now;\n deps.log(\n `[disk-guard] HELD${deps.force ? ' (forced \"Update CLI now\")' : ''} self-update: ` +\n (why === 'lock'\n ? 'a package install or cache cleanup has held the package cache for too long; '\n : 'another npm/pnpm install is running on this host; ') +\n 'not starting `npm install -g` alongside it. Retrying on a later check' +\n (first\n ? ' (further busy holds this episode are logged hourly)'\n : `. STILL held after ${heldMin}m: if no install should be running, find and stop the stuck npm/pnpm process`) +\n ' [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: null, cleaned: false, reason: 'package_cache_busy' };\n };\n const isBusyRaw = async () => (deps.packageManagerBusy ? await deps.packageManagerBusy().catch(() => true) : false);\n // Busy, unless we have already deferred for longer than the ceiling.\n const isBusy = async () => {\n if (!(await isBusyRaw())) {\n // Obi Wan 417034a6c12b: the ceiling bounds CONTINUOUS deferral, so an idle\n // reading ends the busy episode even when the update then holds on disk.\n busyEpisode = null;\n return false;\n }\n const now = deps.nowMs ?? Date.now();\n if (busyEpisode && now - busyEpisode.since >= SELF_UPDATE_BUSY_CEILING_MS) {\n deps.log(\n `[disk-guard] self-update has deferred to another npm/pnpm install for ${Math.round((now - busyEpisode.since) / 60_000)}m, ` +\n 'past the ceiling; proceeding anyway so a stuck or looping install cannot block updates forever [ENG-11669]',\n );\n return false;\n }\n return true;\n };\n\n // Obi Wan a2c6f266bf18: take the package-cache lock (bounded) and KEEP it from\n // these checks through the install. Nothing queued behind it, our own cleanup\n // included, can start between \"there is room and nothing else is installing\"\n // and the install itself. The caller releases it after the install.\n const release = await acquirePackageCacheLock({\n maxWaitMs: deps.idleWaitMs ?? PACKAGE_CACHE_IDLE_MAX_WAIT_MS,\n maxHoldMs: SELF_UPDATE_LOCK_MAX_HOLD_MS,\n label: 'the agt self-update',\n log: deps.log,\n });\n if (!release) return busyHold('lock');\n try {\n if (await isBusy()) {\n release();\n return busyHold('pgrep');\n }\n const room = await ensureDiskForSelfUpdateLocked(deps);\n if (!room.ok) {\n release();\n return room;\n }\n // Re-checked last: an install outside the lock may have started meanwhile.\n if (await isBusy()) {\n release();\n return busyHold('pgrep');\n }\n busyEpisode = null;\n return { ...room, release };\n } catch (err) {\n release();\n throw err;\n }\n}\n\n/** The disk decision, run while the caller holds the package-cache lock. */\nasync function ensureDiskForSelfUpdateLocked(\n deps: DiskGuardDeps & { nowMs?: number; force?: boolean },\n): Promise<{ ok: true; freeBytes: number | null; cleaned: boolean } | Exclude<SelfUpdateRoom, { ok: true }>> {\n const nowMs = deps.nowMs ?? Date.now();\n const before = deps.readStat();\n if (!before) {\n deps.log('[disk-guard] could not read free space on the install filesystem; proceeding with the self-update unchecked [ENG-11669]');\n return { ok: true, freeBytes: null, cleaned: false };\n }\n if (before.freeBytes >= SELF_UPDATE_MIN_FREE_BYTES) {\n if (holdEpisode) deps.log(`[disk-guard] self-update hold cleared: ${mb(before.freeBytes)} free [ENG-11669]`);\n holdEpisode = null;\n forcedHoldAnnounced = false;\n return { ok: true, freeBytes: before.freeBytes, cleaned: false };\n }\n\n // Already held this episode and cleaned recently: refuse quietly. Cleaning\n // again would just repeat a cleanup that already did not make room.\n if (holdEpisode && nowMs - holdEpisode.lastCleanupMs < DISK_GUARD_INTERVAL_MS) {\n // A forced \"Update CLI now\" is a human waiting on an answer, and human-paced,\n // so it is always said (Obi Wan b07e89e3f0a2; the device-state hold's\n // forced announce is the precedent). The scheduled checks stay quiet.\n if (deps.force && !forcedHoldAnnounced) {\n forcedHoldAnnounced = true;\n deps.log(\n `[disk-guard] HELD (forced \"Update CLI now\"): only ${mb(before.freeBytes)} free for the install ` +\n `(need ${mb(SELF_UPDATE_MIN_FREE_BYTES)}); cleanup already ran this hour and did not make room. ` +\n 'Free disk on this host; the request stays pending and applies once there is room ' +\n '(said once per request) [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: before.freeBytes, cleaned: true };\n }\n\n const firstHold = holdEpisode === null;\n if (firstHold) {\n deps.log(\n `[disk-guard] only ${mb(before.freeBytes)} free for the install (need ${mb(SELF_UPDATE_MIN_FREE_BYTES)} to self-update); ` +\n 'running safe cleanup first [ENG-11669]',\n );\n }\n // We already hold the lock: call the cleanup directly (exclusiveCleanup would\n // queue behind ourselves).\n const results = await deps.cleanup();\n const after = deps.readStat();\n const freeAfter = after?.freeBytes ?? before.freeBytes;\n if (freeAfter >= SELF_UPDATE_MIN_FREE_BYTES) {\n deps.log(`[disk-guard] cleanup freed space: ${mb(before.freeBytes)} -> ${mb(freeAfter)} (${summarize(results)}) [ENG-11669]`);\n holdEpisode = null;\n return { ok: true, freeBytes: freeAfter, cleaned: true };\n }\n holdEpisode = { lastCleanupMs: nowMs };\n if (!firstHold && deps.force && !forcedHoldAnnounced) {\n forcedHoldAnnounced = true;\n // Obi Wan e3f6b7a31ff4: a forced update that lands on the hourly retry is\n // refused here too, and must say so like the early-return branch does.\n deps.log(\n `[disk-guard] HELD (forced \"Update CLI now\"): still only ${mb(freeAfter)} free after cleanup ` +\n `(need ${mb(SELF_UPDATE_MIN_FREE_BYTES)}; ${summarize(results)}). Free disk on this host, then retry [ENG-11669]`,\n );\n }\n if (firstHold) {\n deps.log(\n `[disk-guard] HELD self-update: still only ${mb(freeAfter)} free after cleanup (${summarize(results)}). ` +\n 'Keeping the current agt binary rather than risk an install that deletes it and then fails with ENOSPC. ' +\n 'Free disk on this host (or grow the volume) and the update applies on a later check. ' +\n '(further holds this episode are not logged) [ENG-11669]',\n );\n }\n return { ok: false, freeBytes: freeAfter, cleaned: true };\n}\n\n/**\n * ENG-11669 (Obi Wan 16f6c25922ae): the stat that bounds an install is the\n * TIGHTER of `/` and the filesystem the install writes to (the npm global\n * prefix or the brew Cellar). On EC2 hosts both are `/`; a host with the prefix\n * on its own mount must not be judged by the wrong disk. Unreadable paths are\n * skipped; all unreadable is null (caller proceeds, logged).\n */\nexport function tightestDiskStat(paths: string[], read: (p: string) => DiskStat | null = readDiskStat): DiskStat | null {\n let best: DiskStat | null = null;\n for (const p of [...new Set(paths)]) {\n const s = read(p);\n if (s && (best === null || s.freeBytes < best.freeBytes)) best = s;\n }\n return best;\n}\n\n/** Module state for the hourly guard. Exported reset for tests. */\nlet lastGuardRunMs: number | null = null;\nexport function resetDiskGuardThrottle(): void {\n lastGuardRunMs = null;\n guardBlindLogged = false;\n}\n\nexport type DiskGuardRun =\n | { ran: false; reason: 'throttled' | 'ok' | 'unknown' }\n | { ran: true; beforeBytes: number; afterBytes: number | null; stillLow: boolean };\n\n/**\n * ENG-11669: the hourly guard. Cheap when healthy (one statfs per hour); only\n * shells out when the disk is actually low. Logs once per run that cleans.\n */\nlet guardBlindLogged = false;\n\nexport async function maybeRunDiskGuard(deps: DiskGuardDeps & { nowMs: number }): Promise<DiskGuardRun> {\n if (lastGuardRunMs !== null && deps.nowMs - lastGuardRunMs < DISK_GUARD_INTERVAL_MS) {\n return { ran: false, reason: 'throttled' };\n }\n lastGuardRunMs = deps.nowMs;\n const before = deps.readStat();\n const verdict = diskVerdict(before);\n if (verdict === 'unknown' || !before) {\n // Obi Wan 21988ab455e0: a blind guard must say so, once per blind spell, or\n // \"healthy\" and \"cannot see\" read the same in manager.log.\n if (!guardBlindLogged) {\n guardBlindLogged = true;\n deps.log(\n '[disk-guard] could not read free space on /; the hourly disk guard is blind (no cleanup, no host_disk_low) ' +\n 'until it can. Said once until the reading comes back [ENG-11669]',\n );\n }\n return { ran: false, reason: 'unknown' };\n }\n if (guardBlindLogged) {\n guardBlindLogged = false;\n deps.log('[disk-guard] free space on / is readable again; the hourly disk guard is back [ENG-11669]');\n }\n if (verdict !== 'low') return { ran: false, reason: 'ok' };\n\n const results = await exclusiveCleanup(deps.cleanup);\n const after = deps.readStat();\n const stillLow = diskVerdict(after) === 'low';\n deps.log(\n `[disk-guard] / was low (${mb(before.freeBytes)} free of ${mb(before.totalBytes)}); safe cleanup ran ` +\n `(${summarize(results)}); now ${after ? mb(after.freeBytes) : 'unknown'} free.` +\n (stillLow\n ? ' STILL LOW: something other than caches is filling this disk. Investigate before it fills: ' +\n '`du -xh --max-depth=2 / | sort -rh | head`. Never delete /root/.claude/projects (agent transcripts). [ENG-11669]'\n : ' [ENG-11669]'),\n );\n return { ran: true, beforeBytes: before.freeBytes, afterBytes: after?.freeBytes ?? null, stillLow };\n}\n"],"mappings":"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","names":["execFileSync","readFileSync","mb","execFileSync","readFileSync"]}