@indigoai-us/hq-cli 5.345.2 → 5.345.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.3] — 2026-10-05
6
+ - Retry a blocker freshness lane-inbox fallback after a transient append failure.
7
+ - Scope bundled-script stdin checks to changes in their workflow, build inputs, CLI sources, tests, or shell assets.
8
+ - Always apply the lane low-disk preflight before starting or resuming workers.
9
+
5
10
  ## [5.345.2] — 2026-10-05
6
11
  - Build and version-check the hq-cloud sync-runner bundle, then validate and atomically stage it with the daemon child bootstrap before releasing the CLI package-use lease.
7
12
  - Defer MCP tool and prompt handler imports until a request needs them so `hq mcp serve` can answer initialize without waiting on the full handler modules.
@@ -13,7 +13,6 @@ import { artifactIdentityKeys, rollbackStorySwitchAttribution, storySwitchNotice
13
13
  import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
14
14
  import { keepAliveExclusion, keepAliveSupersededBy, LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
15
15
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
16
- import { lowDiskGuardEnabledForLane } from "../lib/lanes/disk-guard.js";
17
16
  import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
18
17
  import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, callerMatchesSessionSenior, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
19
18
  export function parseMonitorLaneMessagePayload(input) {
@@ -782,7 +781,6 @@ export function createLaneMessageHandlers(deps) {
782
781
  const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
783
782
  if (admission.action === "refuse" && admission.error)
784
783
  throw admission.error;
785
- const guardEnabled = admission.action === "queue" ? false : await lowDiskGuardEnabledForLane(hqRoot, lane);
786
784
  return withResumeHireAdmission(ctx, lane, () => {
787
785
  if (admission.action === "queue") {
788
786
  const sessionId = lane.provider_session_id;
@@ -826,7 +824,6 @@ export function createLaneMessageHandlers(deps) {
826
824
  return queued;
827
825
  }
828
826
  const started = spawnLaneWorker(hqRoot, lane, {
829
- lowDiskGuardEnabled: guardEnabled,
830
827
  env: ctx.env,
831
828
  modelDeps: {
832
829
  readFileSync: ctx.readFileSync,
@@ -7,7 +7,6 @@ import { assertNoSecretText } from "../lib/lanes/text-scan.js";
7
7
  import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
8
8
  import { artifactIdentityKeys, rollbackStorySwitchAttribution, storySwitchNotice } from "../lib/lanes/story-switch.js";
9
9
  import { spawnLaneWorker } from "../lib/lanes/spawn.js";
10
- import { lowDiskGuardEnabledForLane } from "../lib/lanes/disk-guard.js";
11
10
  import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, hasKeepAliveProgress, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
12
11
  export function createLaneResumeHandlers(deps) {
13
12
  const { archiveResumeMessage, assertLaneRunsHereForAdmission, assertLaneRunsHereForOperatorMutation, assessLaneAdmission, automaticBalanceMoveHistoryReason, authorizeFailedResume, authorizeResumeProfileRepin, casUpdateLane, dispatchOutpostLaneCommand, laneIsHostedHere, laneWithRecoveredProviderSession, mintMessageId, persistClaudeAccountSelection, prepareAccountMove, printJson, recordResumeSetup, resolveResumeProfile, selectAutomaticClaudeResumeAccount, validateResumeProfileModel, withResumeHireAdmission } = deps;
@@ -448,7 +447,6 @@ export function createLaneResumeHandlers(deps) {
448
447
  const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
449
448
  if (admission.action === "refuse" && admission.error)
450
449
  throw admission.error;
451
- const guardEnabled = admission.action === "queue" ? false : await lowDiskGuardEnabledForLane(hqRoot, lane);
452
450
  return withResumeHireAdmission(ctx, lane, () => {
453
451
  if (admission.action === "queue") {
454
452
  const sessionId = lane.provider_session_id;
@@ -492,7 +490,6 @@ export function createLaneResumeHandlers(deps) {
492
490
  });
493
491
  }
494
492
  const started = spawnLaneWorker(hqRoot, lane, {
495
- lowDiskGuardEnabled: guardEnabled,
496
493
  env: ctx.env,
497
494
  modelDeps: {
498
495
  readFileSync: ctx.readFileSync,
@@ -37,7 +37,7 @@ import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
37
37
  import { runResidentCommandWithPackageLease } from "../utils/resident-package-preload.js";
38
38
  import { readLanesFlagLocalOverride, readLanesRegistryFlag, readLanesRegistryFlagFailOff } from "../lib/lanes/registry-flag.js";
39
39
  import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_LIST_SKIP_REMOTE_REFRESH_FLAG, LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG, LANE_REUSE_REFRESHES_PROFILE_FLAG, } from "../lib/lanes/flag-keys.js";
40
- import { configuredCodexRolloutHomes, lowDiskGuardEnabledForLane, retainCodexRollouts } from "../lib/lanes/disk-guard.js";
40
+ import { configuredCodexRolloutHomes, retainCodexRollouts } from "../lib/lanes/disk-guard.js";
41
41
  import { isControllerAliasOutpostId } from "../lib/lanes/admission-host.js";
42
42
  import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
43
43
  import { syncOutpostLeadRegistries } from "../lib/lanes/autopilot-registry.js";
@@ -3735,7 +3735,6 @@ async function runCreate(ctx, opts) {
3735
3735
  let started = lane;
3736
3736
  if (admission.action === "start") {
3737
3737
  started = spawnLaneWorker(hqRoot, lane, {
3738
- lowDiskGuardEnabled: await lowDiskGuardEnabledForLane(hqRoot, lane),
3739
3738
  env: ctx.env,
3740
3739
  modelDeps: {
3741
3740
  readFileSync: ctx.readFileSync,
@@ -4877,7 +4876,6 @@ async function runPickup(ctx, opts) {
4877
4876
  }
4878
4877
  try {
4879
4878
  return spawnLaneWorker(hqRoot, current, {
4880
- lowDiskGuardEnabled: await lowDiskGuardEnabledForLane(hqRoot, current),
4881
4879
  env: ctx.env,
4882
4880
  modelDeps: {
4883
4881
  readFileSync: ctx.readFileSync,
@@ -6252,7 +6250,6 @@ async function runAdmit(ctx, laneId, opts) {
6252
6250
  return;
6253
6251
  }
6254
6252
  const started = spawnLaneWorker(hqRoot, lane, {
6255
- lowDiskGuardEnabled: await lowDiskGuardEnabledForLane(hqRoot, lane),
6256
6253
  env: ctx.env,
6257
6254
  modelDeps: {
6258
6255
  readFileSync: ctx.readFileSync,
@@ -1,14 +1,13 @@
1
1
  import * as fs from "node:fs";
2
- import { type ReadLanesFlagOptions } from "./registry-flag.js";
3
- import type { LaneRecord, LanesConfig } from "./types.js";
4
- export declare const LOW_DISK_GUARD_FLAG = "lanes.low-disk-guard";
5
- export declare function lowDiskGuardEnabledForLane(hqRoot: string, lane: LaneRecord, readFlag?: (key: string, options: ReadLanesFlagOptions) => Promise<boolean>): Promise<boolean>;
2
+ import type { LaneProvider, LanesConfig } from "./types.js";
6
3
  interface DiskStats {
7
4
  bsize: number;
8
5
  blocks: number;
9
6
  bavail: number;
10
7
  }
11
8
  export type StatFs = (target: string) => DiskStats;
9
+ /** Resolve the enabled provider home a lane can use for its disk preflight. */
10
+ export declare function laneDiskAccount(config: LanesConfig, provider: LaneProvider, preferredAccountId?: string): import("./types.js").AccountConfig | undefined;
12
11
  export interface DiskSpaceMeasurement {
13
12
  path: string;
14
13
  free_bytes: number;
@@ -3,17 +3,18 @@ import * as path from "node:path";
3
3
  import { createGzip } from "node:zlib";
4
4
  import { createReadStream, createWriteStream } from "node:fs";
5
5
  import { pipeline } from "node:stream/promises";
6
- import { readLanesRegistryFlag } from "./registry-flag.js";
7
- import { resolveLaneCompany } from "./lane-company.js";
8
6
  import { effectiveAccountHome } from "./account-home.js";
9
7
  import { lanesConfigPath } from "./config.js";
10
- export const LOW_DISK_GUARD_FLAG = "lanes.low-disk-guard";
11
8
  const DEFAULT_LOW_DISK_FREE_BYTES = 5 * 1024 ** 3;
12
9
  const DEFAULT_LOW_DISK_FRACTION = 0.02;
13
10
  const EXCLUDED_SESSION_FILES = new Set(["auth.json", ".credentials.json", "thread_history.sqlite"]);
14
- export async function lowDiskGuardEnabledForLane(hqRoot, lane, readFlag = readLanesRegistryFlag) {
15
- const company = resolveLaneCompany(hqRoot, lane).company;
16
- return readFlag(LOW_DISK_GUARD_FLAG, { company, companyUidSource: "resolved_lane_company" });
11
+ /** Resolve the enabled provider home a lane can use for its disk preflight. */
12
+ export function laneDiskAccount(config, provider, preferredAccountId) {
13
+ const accounts = config.providers[provider].accounts;
14
+ const preferred = preferredAccountId ? accounts[preferredAccountId] : undefined;
15
+ if (preferred?.enabled)
16
+ return preferred;
17
+ return Object.values(accounts).find((account) => account.enabled);
17
18
  }
18
19
  export function measureDiskSpace(target, statfs = (p) => fs.statfsSync(p), minFreeBytes = DEFAULT_LOW_DISK_FREE_BYTES) {
19
20
  const stats = statfs(target);
@@ -23,7 +23,7 @@ import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionReco
23
23
  import { applyThrottleDecision, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyObservation, recordCpuBusyObservation, throttleStatePath, writeThrottleState, } from "./throttle.js";
24
24
  import { readLanesRegistryFlag, readLanesRegistryFlagFailOff } from "./registry-flag.js";
25
25
  import { LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_LEAD_ADMISSION_PRIORITY_FLAG } from "./flag-keys.js";
26
- import { checkLaneDiskSpace, configuredCodexRolloutHomes, lowDiskGuardEnabledForLane, retainCodexRollouts } from "./disk-guard.js";
26
+ import { checkLaneDiskSpace, configuredCodexRolloutHomes, laneDiskAccount, retainCodexRollouts } from "./disk-guard.js";
27
27
  import { laneCompanyUidSource, resolveLaneCompany } from "./lane-company.js";
28
28
  import { appendDropboxMessage } from "./dropbox.js";
29
29
  import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolGuard, RUNAWAY_TOOL_GUARD_FLAG, } from "./runaway-tool-guard.js";
@@ -1452,31 +1452,27 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
1452
1452
  repairCandidate &&
1453
1453
  repairCandidate.envelope === null &&
1454
1454
  (!repairCandidate.missing || envelopeRepairMode(classified.provider) === "resume")) {
1455
- if (await lowDiskGuardEnabledForLane(hqRoot, classified, deps.readRegistryFlag)) {
1456
- const repairConfig = loadLanesConfig(hqRoot);
1457
- const repairAccount = classified.account
1458
- ? repairConfig.providers[classified.provider].accounts[classified.account]
1459
- : undefined;
1460
- if (!repairAccount) {
1461
- throw new LanesError("low_disk_account_unavailable", `Cannot determine provider home for envelope repair of lane ${classified.lane_id}`, { lane_id: classified.lane_id });
1462
- }
1463
- const providerHome = effectiveAccountHome(repairAccount, deps.host.hostname).home;
1464
- const free = checkLaneDiskSpace(hqRoot, providerHome, deps.spawnLaneDeps?.statfs, repairConfig.low_disk_min_free_bytes);
1465
- if (free.low) {
1466
- const measuredAt = now.toISOString();
1467
- updateLane(hqRoot, classified.lane_id, classified.version, "reconcile", (current) => ({
1468
- ...current,
1469
- state: "queued",
1470
- queued_reason: "low_disk",
1471
- queued_details: { reason: "low_disk", hq_free_bytes: free.hq.free_bytes, provider_free_bytes: free.provider.free_bytes, measured_at: measuredAt },
1472
- queued_at: current.queued_at ?? measuredAt,
1473
- admission_reserved_at: undefined,
1474
- terminal_reason: undefined,
1475
- terminal_details: undefined,
1476
- }));
1477
- summary.skipped.push({ kind: "lane", id: classified.lane_id, why: `low_disk: hq=${free.hq.free_bytes} provider=${free.provider.free_bytes}` });
1478
- continue;
1479
- }
1455
+ const repairConfig = loadLanesConfig(hqRoot);
1456
+ const repairAccount = laneDiskAccount(repairConfig, classified.provider, classified.account);
1457
+ if (!repairAccount) {
1458
+ throw new LanesError("low_disk_account_unavailable", `Cannot determine provider home for envelope repair of lane ${classified.lane_id}`, { lane_id: classified.lane_id });
1459
+ }
1460
+ const providerHome = effectiveAccountHome(repairAccount, deps.host.hostname).home;
1461
+ const free = checkLaneDiskSpace(hqRoot, providerHome, deps.spawnLaneDeps?.statfs, repairConfig.low_disk_min_free_bytes);
1462
+ if (free.low) {
1463
+ const measuredAt = now.toISOString();
1464
+ updateLane(hqRoot, classified.lane_id, classified.version, "reconcile", (current) => ({
1465
+ ...current,
1466
+ state: "queued",
1467
+ queued_reason: "low_disk",
1468
+ queued_details: { reason: "low_disk", hq_free_bytes: free.hq.free_bytes, provider_free_bytes: free.provider.free_bytes, measured_at: measuredAt },
1469
+ queued_at: current.queued_at ?? measuredAt,
1470
+ admission_reserved_at: undefined,
1471
+ terminal_reason: undefined,
1472
+ terminal_details: undefined,
1473
+ }));
1474
+ summary.skipped.push({ kind: "lane", id: classified.lane_id, why: `low_disk: hq=${free.hq.free_bytes} provider=${free.provider.free_bytes}` });
1475
+ continue;
1480
1476
  }
1481
1477
  const spawnRepair = deps.spawnRepair ??
1482
1478
  ((root, repairLane, input) => spawnEnvelopeRepair(root, repairLane, input, spawnDepsForHost(deps)));
@@ -1848,7 +1844,6 @@ async function admitQueued(hqRoot, lanes, summary, deps, now, onlyLaneIds, exclu
1848
1844
  const spawnFn = deps.spawn ??
1849
1845
  (async (root, lane) => spawnLaneWorker(root, lane, {
1850
1846
  ...spawnDepsForHost(deps),
1851
- lowDiskGuardEnabled: await lowDiskGuardEnabledForLane(root, lane, deps.readRegistryFlag),
1852
1847
  }));
1853
1848
  const running = lanes.filter((lane) => (lane.state === "running" ||
1854
1849
  hasFreshAdmissionReservation(lane, now.getTime()) ||
@@ -3292,7 +3287,6 @@ async function resumeAwaiting(hqRoot, lanes, questions, summary, deps, onlyLaneI
3292
3287
  }
3293
3288
  const spawnFn = deps.spawn ?? (async (root, lane) => spawnLaneWorker(root, lane, {
3294
3289
  ...spawnDepsForHost(deps),
3295
- lowDiskGuardEnabled: await lowDiskGuardEnabledForLane(root, lane, deps.readRegistryFlag),
3296
3290
  }));
3297
3291
  const nowMs = deps.now().getTime();
3298
3292
  const fallbackAnswerAt = new Date(nowMs).toISOString();
@@ -75,8 +75,6 @@ export interface SpawnLaneDeps {
75
75
  hostname?: string;
76
76
  /** The caller's host scope for admission checks; reconcile supplies its resolved host. */
77
77
  admissionHost?: AdmissionHost;
78
- /** Resolved per-company by the async command/reconcile boundary. */
79
- lowDiskGuardEnabled?: boolean;
80
78
  /** Injected statfs boundary; production uses node:fs.statfsSync. */
81
79
  statfs?: StatFs;
82
80
  /** Provider model catalog dependencies, shared with create in tests. */
@@ -36,7 +36,7 @@ import { MESSAGE_REPLAY_STATE_NAME } from "./message-replay.js";
36
36
  import { markAnsweredQuestionsDelivered } from "./questions.js";
37
37
  import { hasEarlierEligibleQueuedLane, laneAdmissionCapReason, withLaneAdmissionLock, } from "./admission.js";
38
38
  import { resolveAdmissionHost } from "./admission-host.js";
39
- import { checkLaneDiskSpace, lowDiskGuardEnabledForLane } from "./disk-guard.js";
39
+ import { checkLaneDiskSpace, laneDiskAccount } from "./disk-guard.js";
40
40
  import { LANE_ENVELOPE_JSON_SCHEMA, ENVELOPE_INVALID_REASON, parseLaneProvider, } from "./types.js";
41
41
  export const LANES_RUNS_REL = path.join("workspace", "lanes-runs");
42
42
  /** Written by reconcile before a resume spawn; the worker prompt names this path. */
@@ -606,7 +606,6 @@ export async function retryLaneOnAlternateAccount(hqRoot, lane, reason, writer =
606
606
  return moved;
607
607
  return spawnLaneWorker(hqRoot, moved, {
608
608
  ...deps,
609
- lowDiskGuardEnabled: deps.lowDiskGuardEnabled ?? await lowDiskGuardEnabledForLane(hqRoot, moved),
610
609
  });
611
610
  });
612
611
  }
@@ -1840,33 +1839,26 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
1840
1839
  probe: modelDeps.probe ?? defaultProbe,
1841
1840
  env,
1842
1841
  };
1843
- if (deps.lowDiskGuardEnabled === true) {
1844
- const providerConfig = config.providers[existing.provider];
1845
- const diskAccount = (existing.account !== undefined ? providerConfig.accounts[existing.account] : undefined) ??
1846
- (providerConfig.default_account !== undefined
1847
- ? providerConfig.accounts[providerConfig.default_account]
1848
- : undefined) ??
1849
- Object.values(providerConfig.accounts).find((account) => account.enabled);
1850
- if (diskAccount !== undefined) {
1851
- const free = checkLaneDiskSpace(hqRoot, effectiveAccountHome(diskAccount, hostname).home, deps.statfs, config.low_disk_min_free_bytes);
1852
- if (free.low) {
1853
- const measuredAt = (deps.now ?? (() => new Date()))().toISOString();
1854
- return retryUpdateLane(hqRoot, lane.lane_id, "spawn", (current) => ({
1855
- ...current,
1856
- state: "queued",
1857
- queued_reason: "low_disk",
1858
- queued_details: {
1859
- reason: "low_disk",
1860
- hq_free_bytes: free.hq.free_bytes,
1861
- provider_free_bytes: free.provider.free_bytes,
1862
- measured_at: measuredAt,
1863
- },
1864
- queued_at: current.queued_at ?? measuredAt,
1865
- admission_reserved_at: undefined,
1866
- terminal_reason: undefined,
1867
- terminal_details: undefined,
1868
- }));
1869
- }
1842
+ const diskAccount = laneDiskAccount(config, existing.provider, existing.account);
1843
+ if (diskAccount !== undefined) {
1844
+ const free = checkLaneDiskSpace(hqRoot, effectiveAccountHome(diskAccount, hostname).home, deps.statfs, config.low_disk_min_free_bytes);
1845
+ if (free.low) {
1846
+ const measuredAt = (deps.now ?? (() => new Date()))().toISOString();
1847
+ return retryUpdateLane(hqRoot, lane.lane_id, "spawn", (current) => ({
1848
+ ...current,
1849
+ state: "queued",
1850
+ queued_reason: "low_disk",
1851
+ queued_details: {
1852
+ reason: "low_disk",
1853
+ hq_free_bytes: free.hq.free_bytes,
1854
+ provider_free_bytes: free.provider.free_bytes,
1855
+ measured_at: measuredAt,
1856
+ },
1857
+ queued_at: current.queued_at ?? measuredAt,
1858
+ admission_reserved_at: undefined,
1859
+ terminal_reason: undefined,
1860
+ terminal_details: undefined,
1861
+ }));
1870
1862
  }
1871
1863
  }
1872
1864
  let selection;
@@ -863,6 +863,12 @@ async function settleBlockerFreshness(hqRoot, lane, manifestTurnId, at, deps, me
863
863
  retryUndeliverable = true;
864
864
  escalationText = `Blocker freshness escalation for ${current.lane_id}: these blockers remain stale after consecutive turns: ${staleLabels.join(", ")}. Status file: ${resolution.statusPath}.`;
865
865
  }
866
+ else if (failedFallback !== undefined) {
867
+ // If the senior inbox is still unavailable, retry the lane inbox fallback.
868
+ deliverFallback = true;
869
+ undeliverableReason = failedFallback.reason ?? prior?.undeliverable_reason ?? "The session senior inbox is unavailable";
870
+ escalationText = `Blocker freshness escalation for ${current.lane_id}: these blockers remain stale after consecutive turns: ${staleLabels.join(", ")}. Status file: ${resolution.statusPath}.`;
871
+ }
866
872
  else if (prior !== undefined && blockerFreshnessFingerprint(prior.stale_keys.map((key, index) => ({ key, label: prior.stale_labels[index] ?? "" }))) !== fingerprint) {
867
873
  escalatedFingerprint = undefined;
868
874
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.2",
3
+ "version": "5.345.3",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {