@indigoai-us/hq-cli 5.345.40 → 5.345.41

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,8 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.41] — 2026-10-07
6
+ - Short-lived hq commands now use a fresh cached company flag snapshot while the registry refresh is pending.
7
+ - Report failed and quota-interrupted keep-alive lead lanes as stranded in the keep-alive audit and `hq doctor`, with the senior recovery command and quota reset time.
8
+
5
9
  ## [5.345.40] — 2026-10-07
6
10
  - Fix published shell parity for rebuild-index company help and Git Bash trigger-facts stdin.
11
+ - Add separate default-off gates: `client-health-show-manual-only` controls CLI remediation output, while `client-health-skip-manual-only-reports` suppresses other manual-only reports and writes a local family diagnostic; `sync.manifest` and `sync.runtime` reports remain filed, and failed automatic fixes remain reportable.
7
12
 
8
13
  ## [5.345.39] — 2026-10-07
9
14
  - Cap each `hq doctor --fix` invocation at two company journal refreshes and persist a rotating cursor so pending journals continue on later runs.
@@ -0,0 +1,7 @@
1
+ /** Doctor alarm for failed lead lanes that are configured to keep running. */
2
+ import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
3
+ export declare const STRANDED_LANES_FAMILY_ID = "stranded-lanes";
4
+ export declare const STRANDED_LANES_FAMILY_TITLE = "Stranded lanes";
5
+ export declare const strandedLanesFamily: CheckFamily;
6
+ export declare function checkStrandedLanes(context: CheckContext): CheckResult[];
7
+ //# sourceMappingURL=stranded-lanes.d.ts.map
@@ -0,0 +1,45 @@
1
+ /** Doctor alarm for failed lead lanes that are configured to keep running. */
2
+ import { readAllLanes } from "../../lanes/store.js";
3
+ export const STRANDED_LANES_FAMILY_ID = "stranded-lanes";
4
+ export const STRANDED_LANES_FAMILY_TITLE = "Stranded lanes";
5
+ export const strandedLanesFamily = {
6
+ id: STRANDED_LANES_FAMILY_ID,
7
+ title: STRANDED_LANES_FAMILY_TITLE,
8
+ run: (context) => Promise.resolve(checkStrandedLanes(context)),
9
+ };
10
+ export function checkStrandedLanes(context) {
11
+ let stranded;
12
+ try {
13
+ stranded = readAllLanes(context.hqRoot).filter((lane) => lane.depth === 0 && lane.keep_alive === true && (lane.state === "failed" ||
14
+ (lane.state === "interrupted" && lane.terminal_reason === "account_quota_exhausted")));
15
+ }
16
+ catch (error) {
17
+ return [{
18
+ status: "UNKNOWN",
19
+ checkId: "stranded-lanes.read-error",
20
+ message: `Could not inspect failed keep-alive lead lanes: ${error instanceof Error ? error.message : String(error)}`,
21
+ }];
22
+ }
23
+ if (stranded.length === 0) {
24
+ return [{
25
+ status: "PASS",
26
+ checkId: "stranded-lanes.none",
27
+ message: "No failed keep-alive lead lanes.",
28
+ }];
29
+ }
30
+ return stranded.map((lane) => {
31
+ const quotaExhausted = lane.terminal_reason === "account_quota_exhausted";
32
+ const state = lane.state;
33
+ return {
34
+ status: "FAIL",
35
+ checkId: `stranded-lanes.${lane.lane_id}`,
36
+ details: {
37
+ lane_id: lane.lane_id,
38
+ ...(lane.terminal_reason ? { terminal_reason: lane.terminal_reason } : {}),
39
+ },
40
+ message: `Keep-alive lead lane ${lane.lane_id} is ${state}${quotaExhausted ? " after account quota exhaustion" : ""}.`,
41
+ remediation: `Ask the senior to recover it with: hq lanes resume ${lane.lane_id} --from-failed --reason "recover stranded keep-alive lane"`,
42
+ };
43
+ });
44
+ }
45
+ //# sourceMappingURL=stranded-lanes.js.map
@@ -4,6 +4,10 @@ export declare const CLIENT_HEALTH_REASON_CODES_FLAG_FALLBACK = false;
4
4
  export declare const CLIENT_HEALTH_REASON_CODES_FLAG_TIMEOUT_MS = 150;
5
5
  export declare const CLIENT_HEALTH_CHECK_STATUS_FLAG = "client-health-sync-check-status";
6
6
  export declare const CLIENT_HEALTH_CHECK_STATUS_FLAG_FALLBACK = false;
7
+ export declare const CLIENT_HEALTH_SHOW_MANUAL_ONLY_FLAG = "client-health-show-manual-only";
8
+ export declare const CLIENT_HEALTH_SHOW_MANUAL_ONLY_FLAG_FALLBACK = false;
9
+ export declare const CLIENT_HEALTH_SKIP_MANUAL_ONLY_REPORTS_FLAG = "client-health-skip-manual-only-reports";
10
+ export declare const CLIENT_HEALTH_SKIP_MANUAL_ONLY_REPORTS_FLAG_FALLBACK = false;
7
11
  type ClientHealthFlagClient = Pick<FlagClient, "ready" | "isEnabled" | "close">;
8
12
  export interface ClientHealthReasonCodesFlagDependencies {
9
13
  env?: NodeJS.ProcessEnv;
@@ -25,6 +29,10 @@ export interface ClientHealthReasonCodesCompany {
25
29
  export declare function clientHealthReasonCodesEnabled(dependencies?: ClientHealthReasonCodesFlagDependencies, callerSignal?: AbortSignal): Promise<boolean>;
26
30
  /** Resolve the separate status-report gate for the active company. */
27
31
  export declare function clientHealthCheckStatusEnabled(dependencies?: ClientHealthReasonCodesFlagDependencies, callerSignal?: AbortSignal): Promise<boolean>;
32
+ /** Resolve the default-off gate for showing manual-only doctor output. */
33
+ export declare function clientHealthShowManualOnlyEnabled(dependencies?: ClientHealthReasonCodesFlagDependencies, callerSignal?: AbortSignal): Promise<boolean>;
34
+ /** Resolve the separate default-off gate for skipping manual-only reports. */
35
+ export declare function clientHealthSkipManualOnlyReportsEnabled(dependencies?: ClientHealthReasonCodesFlagDependencies, callerSignal?: AbortSignal): Promise<boolean>;
28
36
  /** Resolve the same default-off gate for one explicitly identified company. */
29
37
  export declare function clientHealthReasonCodesEnabledForCompany(company: ClientHealthReasonCodesCompany, dependencies?: ClientHealthReasonCodesFlagDependencies, callerSignal?: AbortSignal): Promise<boolean>;
30
38
  /** Resolve the separate default-off check-status gate for one company. */
@@ -6,6 +6,10 @@ export const CLIENT_HEALTH_REASON_CODES_FLAG_FALLBACK = false;
6
6
  export const CLIENT_HEALTH_REASON_CODES_FLAG_TIMEOUT_MS = 150;
7
7
  export const CLIENT_HEALTH_CHECK_STATUS_FLAG = "client-health-sync-check-status";
8
8
  export const CLIENT_HEALTH_CHECK_STATUS_FLAG_FALLBACK = false;
9
+ export const CLIENT_HEALTH_SHOW_MANUAL_ONLY_FLAG = "client-health-show-manual-only";
10
+ export const CLIENT_HEALTH_SHOW_MANUAL_ONLY_FLAG_FALLBACK = false;
11
+ export const CLIENT_HEALTH_SKIP_MANUAL_ONLY_REPORTS_FLAG = "client-health-skip-manual-only-reports";
12
+ export const CLIENT_HEALTH_SKIP_MANUAL_ONLY_REPORTS_FLAG_FALLBACK = false;
9
13
  function safeErrorClass(error) {
10
14
  const name = error instanceof Error ? error.name : "UnknownError";
11
15
  return /^[A-Za-z][A-Za-z0-9]{0,63}$/.test(name) ? name : "UnknownError";
@@ -44,6 +48,24 @@ export async function clientHealthCheckStatusEnabled(dependencies = {}, callerSi
44
48
  companySlug: env.HQ_COMPANY_SLUG?.trim() || undefined,
45
49
  }, dependencies, callerSignal);
46
50
  }
51
+ /** Resolve the default-off gate for showing manual-only doctor output. */
52
+ export async function clientHealthShowManualOnlyEnabled(dependencies = {}, callerSignal) {
53
+ const env = dependencies.env ?? process.env;
54
+ const companyUid = env.HQ_COMPANY_UID?.trim() || "";
55
+ if (!/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid)) {
56
+ return CLIENT_HEALTH_SHOW_MANUAL_ONLY_FLAG_FALLBACK;
57
+ }
58
+ return clientHealthFlagEnabledForCompany(CLIENT_HEALTH_SHOW_MANUAL_ONLY_FLAG, CLIENT_HEALTH_SHOW_MANUAL_ONLY_FLAG_FALLBACK, { companyUid, companySlug: env.HQ_COMPANY_SLUG?.trim() || undefined }, dependencies, callerSignal);
59
+ }
60
+ /** Resolve the separate default-off gate for skipping manual-only reports. */
61
+ export async function clientHealthSkipManualOnlyReportsEnabled(dependencies = {}, callerSignal) {
62
+ const env = dependencies.env ?? process.env;
63
+ const companyUid = env.HQ_COMPANY_UID?.trim() || "";
64
+ if (!/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid)) {
65
+ return CLIENT_HEALTH_SKIP_MANUAL_ONLY_REPORTS_FLAG_FALLBACK;
66
+ }
67
+ return clientHealthFlagEnabledForCompany(CLIENT_HEALTH_SKIP_MANUAL_ONLY_REPORTS_FLAG, CLIENT_HEALTH_SKIP_MANUAL_ONLY_REPORTS_FLAG_FALLBACK, { companyUid, companySlug: env.HQ_COMPANY_SLUG?.trim() || undefined }, dependencies, callerSignal);
68
+ }
47
69
  /** Resolve the same default-off gate for one explicitly identified company. */
48
70
  export async function clientHealthReasonCodesEnabledForCompany(company, dependencies = {}, callerSignal) {
49
71
  return clientHealthFlagEnabledForCompany(CLIENT_HEALTH_REASON_CODES_FLAG, CLIENT_HEALTH_REASON_CODES_FLAG_FALLBACK, company, dependencies, callerSignal);
@@ -41,6 +41,7 @@ import { botDir } from "../../bot/paths.js";
41
41
  import { readBotConfig } from "../../bot/config.js";
42
42
  import { buildBotDaemonPaths, rewriteBotDaemonNodePath } from "../../bot/daemon.js";
43
43
  import { classifyRefreshSyncOutcome, } from "./refresh-sync-outcome.js";
44
+ import { clientHealthShowManualOnlyEnabled } from "../client-health-reason-codes-flag.js";
44
45
  /** The tree subtrees whose uncommitted changes block a `--fix` run. */
45
46
  export const HOOK_CONFIG_DIRS = [".claude", ".codex", ".grok"];
46
47
  /** Per-company bound for the default `hq sync pull` repair. */
@@ -117,6 +118,17 @@ export async function applyFixes(options) {
117
118
  return empty({ exitCode: 1, refused: "dirty-tree" });
118
119
  }
119
120
  }
121
+ // Resolve the customer-visible manual-only output gate once per fix run.
122
+ // Keep it behind the dirty-tree guard, and fail open to the pre-change
123
+ // output if the identity is absent or the registry lookup fails.
124
+ let showManualOnlyRemediations = false;
125
+ try {
126
+ showManualOnlyRemediations =
127
+ await clientHealthShowManualOnlyEnabled();
128
+ }
129
+ catch {
130
+ writeErr("HQ client-health manual-only flag lookup failed; keeping manual-only instructions hidden.\n");
131
+ }
120
132
  const collect = options.runChecks ?? defaultRunChecks;
121
133
  const refreshSync = options.refreshSync ?? defaultRefreshSync;
122
134
  const rewriteBotNodePath = options.rewriteBotNodePath ?? defaultRewriteBotNodePath;
@@ -174,6 +186,8 @@ export async function applyFixes(options) {
174
186
  refused = "declined";
175
187
  if (planned.length === 0) {
176
188
  write("Aborted — no changes written.\n");
189
+ if (showManualOnlyRemediations)
190
+ writeManualOnlyRemediations(findings, write);
177
191
  return empty({ refused, fixableCount });
178
192
  }
179
193
  write(`Pass ${pass} declined — no further changes written.\n`);
@@ -214,6 +228,8 @@ export async function applyFixes(options) {
214
228
  }
215
229
  if (planned.length === 0) {
216
230
  write("hq doctor --fix: nothing to repair — no auto-fixable findings.\n");
231
+ if (showManualOnlyRemediations)
232
+ writeManualOnlyRemediations(findings, write);
217
233
  return empty({ refused, fixableCount });
218
234
  }
219
235
  // Show each repair's final post-fix status, including work from earlier passes.
@@ -243,6 +259,8 @@ export async function applyFixes(options) {
243
259
  if (planned.at(-1)?.pass === MAX_FIX_PASSES && buildPlans(findings).length > 0) {
244
260
  write(`Stopped after ${MAX_FIX_PASSES} repair passes; new auto-fixable findings remain.\n`);
245
261
  }
262
+ if (showManualOnlyRemediations)
263
+ writeManualOnlyRemediations(findings, write);
246
264
  return {
247
265
  exitCode: 0,
248
266
  wrote: true,
@@ -254,6 +272,24 @@ export async function applyFixes(options) {
254
272
  applied,
255
273
  };
256
274
  }
275
+ /** Print the available user action for findings that `--fix` deliberately skips. */
276
+ function writeManualOnlyRemediations(findings, write) {
277
+ const manual = findings.flatMap((result) => {
278
+ const remediation = deriveRemediation(result);
279
+ return remediation && !remediation.autoFixable
280
+ ? [{ result, remediation }]
281
+ : [];
282
+ });
283
+ if (manual.length === 0)
284
+ return;
285
+ write("\nManual-only findings — complete these actions yourself:\n");
286
+ for (const { result, remediation } of manual) {
287
+ write(` ${result.checkId}: ${remediation.action}\n`);
288
+ if (remediation.command.trim()) {
289
+ write(` Command: ${remediation.command}\n`);
290
+ }
291
+ }
292
+ }
257
293
  /** Run the default (read-only) check registry and flatten to a result list. */
258
294
  async function defaultRunChecks(hqRoot) {
259
295
  // `--fix` never repairs the integrations family (networked inventory), so
@@ -25,6 +25,7 @@ import { workContextFamily } from "./checks/work-context.js";
25
25
  import { botsFamily } from "./checks/bots.js";
26
26
  import { anywhereFamily } from "./checks/anywhere.js";
27
27
  import { scaffoldCliFloorFamily } from "./checks/scaffold-cli-floor.js";
28
+ import { strandedLanesFamily } from "./checks/stranded-lanes.js";
28
29
  import { fixtureCoverageFamily } from "./fixtures/discover.js";
29
30
  import { checkClaudeWiring } from "./checks/claude-wiring.js";
30
31
  /**
@@ -266,6 +267,9 @@ export function createDefaultRegistry() {
266
267
  // HQ anywhere (US-012): pointer, registry, hqd service/socket/version,
267
268
  // heartbeat, MCP registrations and skill links. NA when not installed.
268
269
  registry.register(anywhereFamily);
270
+ // Failed keep-alive lead lanes cannot recover themselves; make them visible
271
+ // in the standard local doctor report with the senior recovery command.
272
+ registry.register(strandedLanesFamily);
269
273
  return registry;
270
274
  }
271
275
  //# sourceMappingURL=registry.js.map
@@ -142,6 +142,16 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
142
142
  const sentry = dependencies.sentry;
143
143
  const companyUid = company.companyUid;
144
144
  const companyIdentifiers = [companyUid, company.companySlug].filter((value) => Boolean(value));
145
+ // Short-lived commands can reach a synchronous gate before the background
146
+ // worker receives the registry response. Hydrate only the validated,
147
+ // company-scoped persisted snapshot so those gates can use the last fresh
148
+ // decision immediately; the worker refresh below replaces it when available.
149
+ const persistedSnapshot = companyUid
150
+ ? readFlagSnapshotFile(companyUid, { env: companyEnv })
151
+ : null;
152
+ if (persistedSnapshot) {
153
+ heldSnapshot = { version: 0, flags: persistedSnapshot.flags };
154
+ }
145
155
  refreshCompletion = Promise.resolve();
146
156
  completeRefresh = null;
147
157
  refreshWorker = null;
@@ -1,4 +1,4 @@
1
- type KeepAliveAuditVerdict = "on_time" | "late" | "early_wake" | "message_wake" | "work_triggered" | "self_wake" | "early_by_keep_alive" | "restarted_untimed" | "not_restarted" | "keep_alive_off" | "waiting";
1
+ type KeepAliveAuditVerdict = "on_time" | "late" | "early_wake" | "message_wake" | "work_triggered" | "self_wake" | "early_by_keep_alive" | "restarted_untimed" | "not_restarted" | "stranded" | "keep_alive_off" | "waiting";
2
2
  interface KeepAliveAuditRestart {
3
3
  at: string | null;
4
4
  trigger: string;
@@ -12,15 +12,18 @@ interface KeepAliveAuditRow {
12
12
  stopped_at: string;
13
13
  next_check_at: string | null;
14
14
  due_at: string;
15
- due_reason: "next_check_at" | "backoff";
15
+ due_reason: "next_check_at" | "backoff" | "quota_reset_at";
16
16
  restart: KeepAliveAuditRestart | null;
17
17
  blocked_outcome?: "woken" | "skipped";
18
18
  blocked_outcome_reason?: string;
19
19
  verdict: KeepAliveAuditVerdict;
20
+ remediation?: string;
21
+ terminal_reason?: string;
20
22
  }
21
23
  interface KeepAliveAuditSummary {
22
24
  by_verdict: Record<KeepAliveAuditVerdict, number>;
23
25
  not_restarted: number;
26
+ stranded: number;
24
27
  restarted_untimed: number;
25
28
  late_restarts_after_window: number;
26
29
  late_over_10m: number;
@@ -4,7 +4,7 @@ import { LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_M
4
4
  import { LanesError } from "./errors.js";
5
5
  import { laneRunDir } from "./spawn.js";
6
6
  import { compareEnvelopeNames } from "./envelope-sequence.js";
7
- import { listQuestionIds, readAllLanes, tryReadQuestion } from "./store.js";
7
+ import { laneRecordPath, listQuestionIds, readAllLanes, tryReadQuestion } from "./store.js";
8
8
  import { isIsoDateTime } from "./types.js";
9
9
  function readTerminalClassifications(hqRoot, laneId) {
10
10
  const root = fs.realpathSync(hqRoot);
@@ -62,6 +62,7 @@ const VERDICTS = [
62
62
  "restarted_untimed",
63
63
  "keep_alive_off",
64
64
  "not_restarted",
65
+ "stranded",
65
66
  "waiting",
66
67
  ];
67
68
  function parseTime(value) {
@@ -70,6 +71,17 @@ function parseTime(value) {
70
71
  const parsed = Date.parse(value);
71
72
  return Number.isFinite(parsed) ? parsed : undefined;
72
73
  }
74
+ function isStrandedKeepAliveLead(lane) {
75
+ return lane.depth === 0 && lane.keep_alive === true && (lane.state === "failed" ||
76
+ (lane.state === "interrupted" && lane.terminal_reason === "account_quota_exhausted"));
77
+ }
78
+ function strandedTerminalTime(lane) {
79
+ if (lane.state === "interrupted")
80
+ return parseTime(lane.interrupted_at);
81
+ if (lane.state === "failed")
82
+ return parseTime(lane.terminal_details?.failed_at);
83
+ return undefined;
84
+ }
73
85
  function readLaneWorkerResumeEvents(hqRoot, laneId) {
74
86
  const root = fs.realpathSync(hqRoot);
75
87
  const eventsPath = path.join(laneRunDir(root, laneId), "watcher", "events.jsonl");
@@ -491,9 +503,45 @@ export function buildKeepAliveAudit(input) {
491
503
  .filter((entry) => entry !== null)
492
504
  .sort((a, b) => a.lane.localeCompare(b.lane));
493
505
  const lanes = allLanes.filter((lane) => (input.laneId === undefined || lane.lane_id === input.laneId) &&
494
- typeof lane.hire_grant === "number" && Number.isFinite(lane.hire_grant) && lane.hire_grant > 0);
506
+ (isStrandedKeepAliveLead(lane) || (typeof lane.hire_grant === "number" && Number.isFinite(lane.hire_grant) && lane.hire_grant > 0)));
495
507
  const rows = [];
496
508
  const errors = [];
509
+ for (const lane of lanes) {
510
+ if (!isStrandedKeepAliveLead(lane))
511
+ continue;
512
+ const terminalAt = strandedTerminalTime(lane);
513
+ if (terminalAt === undefined) {
514
+ errors.push({
515
+ lane: lane.lane_id,
516
+ error: "keep_alive_audit_terminal_time_missing",
517
+ path: laneRecordPath(input.hqRoot, lane.lane_id),
518
+ message: `Cannot determine terminal transition time for stranded keep-alive lead ${lane.lane_id}.`,
519
+ });
520
+ continue;
521
+ }
522
+ if (terminalAt < sinceMs || terminalAt > nowMs)
523
+ continue;
524
+ const dueCandidates = [
525
+ { at: parseTime(lane.last_envelope?.next_check_at), reason: "next_check_at" },
526
+ { at: parseTime(lane.keep_alive_next_eligible_at), reason: "backoff" },
527
+ { at: parseTime(lane.terminal_details?.quota_reset_at) ?? parseTime(lane.quota_reset_at), reason: "quota_reset_at" },
528
+ ].filter((candidate) => candidate.at !== undefined);
529
+ dueCandidates.sort((a, b) => b.at - a.at);
530
+ const due = dueCandidates[0] ?? { at: terminalAt, reason: "backoff" };
531
+ rows.push({
532
+ lane: lane.lane_id,
533
+ project: lane.project_id,
534
+ profile: lane.worker_profile ?? lane.worker ?? null,
535
+ stopped_at: iso(terminalAt),
536
+ next_check_at: lane.last_envelope?.next_check_at ?? null,
537
+ due_at: iso(due.at),
538
+ due_reason: due.reason,
539
+ restart: null,
540
+ verdict: "stranded",
541
+ ...(lane.terminal_reason === undefined ? {} : { terminal_reason: lane.terminal_reason }),
542
+ remediation: `Ask the senior to run: hq lanes resume ${lane.lane_id} --from-failed --reason "recover stranded keep-alive lane"`,
543
+ });
544
+ }
497
545
  for (const lane of lanes) {
498
546
  try {
499
547
  const { allMtimes, stops } = readStopEnvelopes(input.hqRoot, lane.lane_id);
@@ -561,6 +609,7 @@ export function buildKeepAliveAudit(input) {
561
609
  summary: {
562
610
  by_verdict: byVerdict,
563
611
  not_restarted: byVerdict.not_restarted,
612
+ stranded: byVerdict.stranded,
564
613
  restarted_untimed: byVerdict.restarted_untimed,
565
614
  late_restarts_after_window: lateRestartsAfterWindow,
566
615
  late_over_10m: lateOverTenMinutes,
@@ -592,8 +641,9 @@ export function formatKeepAliveAuditText(result) {
592
641
  ...values.map(tableLine),
593
642
  "",
594
643
  `Since ${result.since}; ${counts}`,
595
- `not_restarted=${result.summary.not_restarted}, late_restarts_after_window=${result.summary.late_restarts_after_window}, late_over_10m=${result.summary.late_over_10m}, early_wake_by_others=${result.summary.early_wake_by_others}, self_wake=${result.summary.self_wake}`,
644
+ `not_restarted=${result.summary.not_restarted}, stranded=${result.summary.stranded}, late_restarts_after_window=${result.summary.late_restarts_after_window}, late_over_10m=${result.summary.late_over_10m}, early_wake_by_others=${result.summary.early_wake_by_others}, self_wake=${result.summary.self_wake}`,
596
645
  `Stopped and not restarted on their own: ${result.summary.stopped_and_not_restarted_on_their_own}`,
646
+ ...result.rows.filter((row) => row.remediation).map((row) => `Stranded ${row.lane}: ${row.remediation}`),
597
647
  ...result.companyless_lead_lanes.map((lane) => `Company-less lead ${lane.lane} (${lane.project}): ${lane.reason}; repair with hq lanes repair-company ${lane.lane} --company <slug>`),
598
648
  ...result.errors.map((error) => `Error ${error.lane} (${error.error}) at ${error.path}: ${error.message}`),
599
649
  ].join("\n");
@@ -1511,7 +1511,7 @@ export function markSpawnFailure(hqRoot, laneId, reasonRaw, details, options = {
1511
1511
  queued_at: undefined,
1512
1512
  admission_reserved_at: undefined,
1513
1513
  terminal_reason: reason,
1514
- terminal_details: details,
1514
+ terminal_details: resumableQuotaHold ? details : { ...details, failed_at: new Date().toISOString() },
1515
1515
  ...(resumableQuotaHold
1516
1516
  ? {
1517
1517
  ...(options.pendingResume !== undefined
@@ -2,6 +2,7 @@ import type { NodeOptions } from "@sentry/node";
2
2
  type SentryModule = typeof import("@sentry/node");
3
3
  type SentryBreadcrumb = Parameters<SentryModule["addBreadcrumb"]>[0];
4
4
  /** Shared Sentry SDK setup for the command graph and isolated fast handlers. */
5
+ export declare function createFlaggedBeforeSend(beforeSend: NodeOptions["beforeSend"], skipManualOnlyReportsEnabled?: () => Promise<boolean>): NonNullable<NodeOptions["beforeSend"]>;
5
6
  export declare function initSentryRuntime(beforeSend: NodeOptions["beforeSend"], overrides?: Partial<Pick<NodeOptions, "transport">>, pendingBreadcrumbs?: readonly SentryBreadcrumb[]): SentryModule | undefined;
6
7
  export declare function finishSentryRuntimeSession(): void;
7
8
  export declare function addSentryRuntimeBreadcrumb(breadcrumb: SentryBreadcrumb): void;
@@ -3,13 +3,116 @@ import { BUNDLED_DSN } from "./sentry-dsn.generated.js";
3
3
  import { CLI_VERSION } from "./cli-version.js";
4
4
  import { beforeBreadcrumb } from "./utils/breadcrumb-buffer.js";
5
5
  import { getCachedSentryUser } from "./utils/sentry-identity.js";
6
- const requireSentry = createRequire(import.meta.url);
6
+ const requireRuntime = createRequire(import.meta.url);
7
7
  let sentryModule;
8
8
  function loadSentryModule() {
9
- sentryModule ??= requireSentry("@sentry/node");
9
+ sentryModule ??= requireRuntime("@sentry/node");
10
10
  return sentryModule;
11
11
  }
12
12
  /** Shared Sentry SDK setup for the command graph and isolated fast handlers. */
13
+ export function createFlaggedBeforeSend(beforeSend, skipManualOnlyReportsEnabled = resolveManualOnlyReportSkip) {
14
+ return async (event, hint) => {
15
+ if (event.tags?.sentry_event_type === "client_health" &&
16
+ event.tags.fix_outcome === "not_auto_fixable") {
17
+ const checkIds = clientHealthCheckIds(event);
18
+ // Sync-family reports detect an open sync P0. Keep filing them until
19
+ // the operator declares that P0 closed.
20
+ if (checkIds.length === 0 ||
21
+ checkIds.some((id) => MANUAL_ONLY_DROP_FAMILY_PREFIXES.some((prefix) => matchesCheckFamily(id, prefix)))) {
22
+ return beforeSend ? beforeSend(event, hint) : event;
23
+ }
24
+ let skipManualOnlyReport = false;
25
+ try {
26
+ skipManualOnlyReport = await skipManualOnlyReportsEnabled();
27
+ }
28
+ catch {
29
+ // The default-off gate fails open to the prior reporting behavior.
30
+ process.stderr.write("HQ client-health manual-only report flag lookup failed; keeping current reporting behavior.\n");
31
+ }
32
+ if (skipManualOnlyReport) {
33
+ recordSkippedManualOnlyReport(checkIds);
34
+ return null;
35
+ }
36
+ }
37
+ return beforeSend ? beforeSend(event, hint) : event;
38
+ };
39
+ }
40
+ const MANUAL_ONLY_DROP_FAMILY_PREFIXES = [
41
+ "sync.manifest",
42
+ "sync.runtime",
43
+ "sync.journal",
44
+ ];
45
+ function clientHealthCheckIds(event) {
46
+ try {
47
+ const tags = event.tags;
48
+ const extra = event.extra;
49
+ const rawValues = [
50
+ tags?.check_ids,
51
+ tags?.check_id,
52
+ tags?.checkIds,
53
+ tags?.checkId,
54
+ tags?.check_class,
55
+ extra?.check_ids,
56
+ extra?.check_id,
57
+ extra?.checkIds,
58
+ extra?.checkId,
59
+ ];
60
+ const ids = new Set();
61
+ let hasUnparseableValue = false;
62
+ for (const raw of rawValues) {
63
+ if (raw === undefined || raw === null)
64
+ continue;
65
+ const candidates = Array.isArray(raw)
66
+ ? raw
67
+ : typeof raw === "string"
68
+ ? raw.split(/[\s,]+/).filter(Boolean)
69
+ : [];
70
+ if (candidates.length === 0) {
71
+ hasUnparseableValue = true;
72
+ continue;
73
+ }
74
+ for (const candidate of candidates) {
75
+ if (typeof candidate === "string" &&
76
+ /^[A-Za-z0-9][A-Za-z0-9_-]*(?:\.[A-Za-z0-9][A-Za-z0-9_-]*)+$/.test(candidate)) {
77
+ ids.add(candidate);
78
+ }
79
+ else {
80
+ hasUnparseableValue = true;
81
+ }
82
+ }
83
+ }
84
+ return hasUnparseableValue ? [] : [...ids];
85
+ }
86
+ catch {
87
+ return [];
88
+ }
89
+ }
90
+ function matchesCheckFamily(checkId, prefix) {
91
+ return checkId === prefix || checkId.startsWith(`${prefix}.`);
92
+ }
93
+ function recordSkippedManualOnlyReport(checkIds) {
94
+ try {
95
+ const families = new Set();
96
+ for (const checkId of checkIds) {
97
+ for (const prefix of MANUAL_ONLY_DROP_FAMILY_PREFIXES) {
98
+ if (matchesCheckFamily(checkId, prefix)) {
99
+ families.add(prefix);
100
+ }
101
+ }
102
+ }
103
+ const familyList = [...families].sort().join(", ") || "unknown";
104
+ process.stderr.write(`HQ client-health manual-only report skipped; check families: ${familyList}.\n`);
105
+ }
106
+ catch {
107
+ // A local diagnostic must not change the decision to drop this event.
108
+ }
109
+ }
110
+ async function resolveManualOnlyReportSkip() {
111
+ // Keep this runtime lookup out of native fast-entry bundles. A static or
112
+ // dynamic import pulls the flag client and its fetch usage into the closure.
113
+ const flagModule = requireRuntime("./lib/doctor/client-health-reason-codes-flag.js");
114
+ return flagModule.clientHealthSkipManualOnlyReportsEnabled();
115
+ }
13
116
  export function initSentryRuntime(beforeSend, overrides = {}, pendingBreadcrumbs = []) {
14
117
  const dsn = BUNDLED_DSN || process.env.SENTRY_DSN;
15
118
  if (!dsn)
@@ -23,7 +126,7 @@ export function initSentryRuntime(beforeSend, overrides = {}, pendingBreadcrumbs
23
126
  release: `hq-cli@${CLI_VERSION}`,
24
127
  environment: process.env.HQ_CLI_ENV ?? "production",
25
128
  initialScope: { tags: { repo: "hq-cli" } },
26
- beforeSend,
129
+ beforeSend: createFlaggedBeforeSend(beforeSend),
27
130
  beforeBreadcrumb,
28
131
  integrations: (defaults) => defaults.filter((integration) => integration.name !== "ProcessSession"),
29
132
  ...overrides,
@@ -1,8 +1,8 @@
1
1
  import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload, } from "./package-use-lease.js";
2
2
  /** Runtime modules that resident commands may need after a wait starts. */
3
3
  export const RESIDENT_PACKAGE_MODULES = {
4
- "agent-kit": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
5
- "agent-mcp": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../commands/access.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "../utils/local-files-overview.js", "@indigoai-us/hq-cloud"],
4
+ "agent-kit": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
5
+ "agent-mcp": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../commands/access.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "../utils/local-files-overview.js", "@indigoai-us/hq-cloud"],
6
6
  login: [
7
7
  "../lib/mesh/api.js",
8
8
  "../lib/work-context/config.js",
@@ -10,7 +10,7 @@ export const RESIDENT_PACKAGE_MODULES = {
10
10
  "../lib/work-context/paths.js",
11
11
  "../lib/plan-limit-nag.js",
12
12
  "../utils/plan-gate-error.js",
13
- "../sentry.js",
13
+ "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client",
14
14
  "open",
15
15
  "../utils/cognito-session.js",
16
16
  "../lib/first-sync-next-step.js",
@@ -21,16 +21,16 @@ export const RESIDENT_PACKAGE_MODULES = {
21
21
  "../lib/daemon/mcp-oauth.js",
22
22
  "../lib/plan-limit-nag.js",
23
23
  "../utils/plan-gate-error.js",
24
- "../sentry.js",
24
+ "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client",
25
25
  "open",
26
26
  "../utils/cognito-session.js",
27
27
  "@indigoai-us/hq-cloud",
28
28
  ],
29
- "agents-terminal": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
29
+ "agents-terminal": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
30
30
  bot: [
31
31
  "../lib/flag-registry.js",
32
32
  "../commands/access.js",
33
- "../sentry.js",
33
+ "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client",
34
34
  "../lib/plan-limit-nag.js",
35
35
  "../utils/plan-gate-error.js",
36
36
  "open",
@@ -40,12 +40,12 @@ export const RESIDENT_PACKAGE_MODULES = {
40
40
  "@indigoai-us/hq-cloud",
41
41
  ],
42
42
  "bot-logs-follow": [],
43
- mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js", "../utils/cognito-session.js", "@indigoai-us/hq-cloud", "../sentry.js"],
43
+ mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js", "../utils/cognito-session.js", "@indigoai-us/hq-cloud", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client"],
44
44
  childWrapper: ["../main.js"],
45
45
  coreJobRun: ["../lib/flag-registry.js"],
46
46
  "daemon-call": ["../lib/daemon/protocol.js"],
47
47
  daemon: [
48
- "../sentry.js",
48
+ "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client",
49
49
  "../utils/cognito-session.js",
50
50
  "../lib/plan-limit-nag.js",
51
51
  "../utils/plan-gate-error.js",
@@ -58,17 +58,17 @@ export const RESIDENT_PACKAGE_MODULES = {
58
58
  "@indigoai-us/hq-cloud",
59
59
  ],
60
60
  "daemon-logs-follow": [],
61
- "mesh-daemon": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
62
- "lanes-watch": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
63
- "monitor-wait": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
64
- "monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
65
- "agents-message-wait": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
66
- "integrations-oauth-wait": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
67
- "outpost-wait": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
61
+ "mesh-daemon": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
62
+ "lanes-watch": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
63
+ "monitor-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
64
+ "monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
65
+ "agents-message-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
66
+ "integrations-oauth-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
67
+ "outpost-wait": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
68
68
  "secrets-sandbox": [],
69
- "pkg-update": ["../sentry.js", "../utils/cognito-session.js", "@indigoai-us/hq-cloud"],
69
+ "pkg-update": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../utils/cognito-session.js", "@indigoai-us/hq-cloud"],
70
70
  "pack-install": [
71
- "../sentry.js",
71
+ "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client",
72
72
  "../lib/plan-limit-nag.js",
73
73
  "../utils/plan-gate-error.js",
74
74
  "open",
@@ -87,6 +87,8 @@ const LOADERS = {
87
87
  "agent-kit": [
88
88
  async () => {
89
89
  const { preloadSentrySdk } = await import("../sentry.js");
90
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
91
+ await import("@indigoai-us/hq-flags-client");
90
92
  preloadSentrySdk();
91
93
  },
92
94
  () => import("../lib/plan-limit-nag.js"),
@@ -99,6 +101,8 @@ const LOADERS = {
99
101
  "agent-mcp": [
100
102
  async () => {
101
103
  const { preloadSentrySdk } = await import("../sentry.js");
104
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
105
+ await import("@indigoai-us/hq-flags-client");
102
106
  preloadSentrySdk();
103
107
  },
104
108
  () => import("../lib/plan-limit-nag.js"),
@@ -119,6 +123,8 @@ const LOADERS = {
119
123
  () => import("../utils/plan-gate-error.js"),
120
124
  async () => {
121
125
  const { preloadSentrySdk } = await import("../sentry.js");
126
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
127
+ await import("@indigoai-us/hq-flags-client");
122
128
  preloadSentrySdk();
123
129
  },
124
130
  () => import("open"),
@@ -133,6 +139,8 @@ const LOADERS = {
133
139
  () => import("../utils/plan-gate-error.js"),
134
140
  async () => {
135
141
  const { preloadSentrySdk } = await import("../sentry.js");
142
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
143
+ await import("@indigoai-us/hq-flags-client");
136
144
  preloadSentrySdk();
137
145
  },
138
146
  () => import("open"),
@@ -142,6 +150,8 @@ const LOADERS = {
142
150
  "agents-terminal": [
143
151
  async () => {
144
152
  const { preloadSentrySdk } = await import("../sentry.js");
153
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
154
+ await import("@indigoai-us/hq-flags-client");
145
155
  preloadSentrySdk();
146
156
  },
147
157
  () => import("../lib/plan-limit-nag.js"),
@@ -156,6 +166,8 @@ const LOADERS = {
156
166
  () => import("../commands/access.js"),
157
167
  async () => {
158
168
  const { preloadSentrySdk } = await import("../sentry.js");
169
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
170
+ await import("@indigoai-us/hq-flags-client");
159
171
  preloadSentrySdk();
160
172
  },
161
173
  () => import("../lib/plan-limit-nag.js"),
@@ -174,6 +186,8 @@ const LOADERS = {
174
186
  () => import("@indigoai-us/hq-cloud"),
175
187
  async () => {
176
188
  const { preloadSentrySdk } = await import("../sentry.js");
189
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
190
+ await import("@indigoai-us/hq-flags-client");
177
191
  preloadSentrySdk();
178
192
  },
179
193
  ],
@@ -189,6 +203,8 @@ const LOADERS = {
189
203
  daemon: [
190
204
  async () => {
191
205
  const { preloadSentrySdk } = await import("../sentry.js");
206
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
207
+ await import("@indigoai-us/hq-flags-client");
192
208
  preloadSentrySdk();
193
209
  },
194
210
  () => import("../utils/cognito-session.js"),
@@ -206,6 +222,8 @@ const LOADERS = {
206
222
  "mesh-daemon": [
207
223
  async () => {
208
224
  const { preloadSentrySdk } = await import("../sentry.js");
225
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
226
+ await import("@indigoai-us/hq-flags-client");
209
227
  preloadSentrySdk();
210
228
  },
211
229
  () => import("../lib/plan-limit-nag.js"),
@@ -218,6 +236,8 @@ const LOADERS = {
218
236
  "lanes-watch": [
219
237
  async () => {
220
238
  const { preloadSentrySdk } = await import("../sentry.js");
239
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
240
+ await import("@indigoai-us/hq-flags-client");
221
241
  preloadSentrySdk();
222
242
  },
223
243
  () => import("../lib/plan-limit-nag.js"),
@@ -230,6 +250,8 @@ const LOADERS = {
230
250
  "monitor-wait": [
231
251
  async () => {
232
252
  const { preloadSentrySdk } = await import("../sentry.js");
253
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
254
+ await import("@indigoai-us/hq-flags-client");
233
255
  preloadSentrySdk();
234
256
  },
235
257
  () => import("../lib/plan-limit-nag.js"),
@@ -245,6 +267,8 @@ const LOADERS = {
245
267
  () => import("../utils/plan-gate-error.js"),
246
268
  async () => {
247
269
  const { preloadSentrySdk } = await import("../sentry.js");
270
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
271
+ await import("@indigoai-us/hq-flags-client");
248
272
  preloadSentrySdk();
249
273
  },
250
274
  () => import("open"),
@@ -255,6 +279,8 @@ const LOADERS = {
255
279
  "agents-message-wait": [
256
280
  async () => {
257
281
  const { preloadSentrySdk } = await import("../sentry.js");
282
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
283
+ await import("@indigoai-us/hq-flags-client");
258
284
  preloadSentrySdk();
259
285
  },
260
286
  () => import("../lib/plan-limit-nag.js"),
@@ -267,6 +293,8 @@ const LOADERS = {
267
293
  "integrations-oauth-wait": [
268
294
  async () => {
269
295
  const { preloadSentrySdk } = await import("../sentry.js");
296
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
297
+ await import("@indigoai-us/hq-flags-client");
270
298
  preloadSentrySdk();
271
299
  },
272
300
  () => import("../lib/plan-limit-nag.js"),
@@ -282,6 +310,8 @@ const LOADERS = {
282
310
  () => import("../utils/plan-gate-error.js"),
283
311
  async () => {
284
312
  const { preloadSentrySdk } = await import("../sentry.js");
313
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
314
+ await import("@indigoai-us/hq-flags-client");
285
315
  preloadSentrySdk();
286
316
  },
287
317
  () => import("open"),
@@ -293,6 +323,8 @@ const LOADERS = {
293
323
  "pkg-update": [
294
324
  async () => {
295
325
  const { preloadSentrySdk } = await import("../sentry.js");
326
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
327
+ await import("@indigoai-us/hq-flags-client");
296
328
  preloadSentrySdk();
297
329
  },
298
330
  () => import("../utils/cognito-session.js"),
@@ -301,6 +333,8 @@ const LOADERS = {
301
333
  "pack-install": [
302
334
  async () => {
303
335
  const { preloadSentrySdk } = await import("../sentry.js");
336
+ await import("../lib/doctor/client-health-reason-codes-flag.js");
337
+ await import("@indigoai-us/hq-flags-client");
304
338
  preloadSentrySdk();
305
339
  },
306
340
  () => import("../lib/plan-limit-nag.js"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.40",
3
+ "version": "5.345.41",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {