@indigoai-us/hq-cli 5.228.1 → 5.228.2

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,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.228.2] — 2026-09-27
6
+
5
7
  ## [5.228.1] — 2026-09-27
6
8
 
7
9
  ### Fixed
@@ -3,6 +3,13 @@ interface CachedWorkerMode {
3
3
  version: number;
4
4
  value: boolean;
5
5
  }
6
+ export type FlagRegistryWorkerModeCacheOperation = "read" | "write";
7
+ export type FlagRegistryWorkerModeCacheStep = "mkdir" | "write_temp" | "link" | "unlink" | "prune" | "readdir" | "read";
8
+ /** Build bounded filesystem diagnostics without exposing the state directory. */
9
+ export declare function flagRegistryWorkerModeCacheFailureContext(error: unknown, operation: FlagRegistryWorkerModeCacheOperation, filePath: string, env?: NodeJS.ProcessEnv): {
10
+ exception: unknown;
11
+ tags: Record<string, string>;
12
+ };
6
13
  /** Keep the last server snapshot decision for this one startup-mode flag. */
7
14
  export declare function flagRegistryWorkerModeCachePath(stateDir: string, scopeIdentifiers: readonly string[]): string;
8
15
  /** A missing, invalid, or inaccessible cache never enables the rollout. */
@@ -1,8 +1,116 @@
1
+ import { execFileSync } from "node:child_process";
1
2
  import { createHash, randomUUID } from "node:crypto";
2
3
  import * as fs from "node:fs";
3
4
  import * as os from "node:os";
4
5
  import * as path from "node:path";
5
6
  export const FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG = "cli.flag-worker-self-contained";
7
+ class FlagRegistryWorkerModeCacheStepError extends Error {
8
+ cacheStep;
9
+ originalError;
10
+ constructor(cacheStep, originalError) {
11
+ super(`Flag registry worker-mode cache failed at ${cacheStep}`);
12
+ this.cacheStep = cacheStep;
13
+ this.originalError = originalError;
14
+ this.name = "FlagRegistryWorkerModeCacheStepError";
15
+ }
16
+ }
17
+ const KNOWN_ERRNOS = new Set([
18
+ "EACCES",
19
+ "EBUSY",
20
+ "EEXIST",
21
+ "EIO",
22
+ "EISDIR",
23
+ "EMFILE",
24
+ "ENFILE",
25
+ "ENOENT",
26
+ "ENOSPC",
27
+ "ENOTDIR",
28
+ "EPERM",
29
+ "EROFS",
30
+ ]);
31
+ function withCacheFsStep(cacheStep, operation) {
32
+ try {
33
+ return operation();
34
+ }
35
+ catch (error) {
36
+ if (error instanceof FlagRegistryWorkerModeCacheStepError)
37
+ throw error;
38
+ throw new FlagRegistryWorkerModeCacheStepError(cacheStep, error);
39
+ }
40
+ }
41
+ function originalCacheError(error) {
42
+ return error instanceof FlagRegistryWorkerModeCacheStepError
43
+ ? error.originalError
44
+ : error;
45
+ }
46
+ function cacheErrorCode(error) {
47
+ const original = originalCacheError(error);
48
+ return typeof original === "object" && original !== null && "code" in original
49
+ ? original.code
50
+ : undefined;
51
+ }
52
+ function directoryHasUchg(directory) {
53
+ if (process.platform !== "darwin")
54
+ return "unknown";
55
+ try {
56
+ const rawFlags = execFileSync("/usr/bin/stat", ["-f", "%f", directory], { timeout: 1000 }).toString("utf8").trim();
57
+ if (!/^\d+$/.test(rawFlags))
58
+ return "unknown";
59
+ const flags = Number(rawFlags);
60
+ if (!Number.isSafeInteger(flags))
61
+ return "unknown";
62
+ return String((flags & 0x00000002) !== 0 || (flags & 0x00020000) !== 0);
63
+ }
64
+ catch {
65
+ // This bounded diagnostic is best-effort; preserve unknown and the original cache error.
66
+ return "unknown";
67
+ }
68
+ }
69
+ /** Build bounded filesystem diagnostics without exposing the state directory. */
70
+ export function flagRegistryWorkerModeCacheFailureContext(error, operation, filePath, env = process.env) {
71
+ const cacheError = error instanceof FlagRegistryWorkerModeCacheStepError
72
+ ? error
73
+ : undefined;
74
+ const directory = path.dirname(filePath);
75
+ let dirExists = "unknown";
76
+ let stats;
77
+ try {
78
+ stats = fs.statSync(directory);
79
+ dirExists = "true";
80
+ }
81
+ catch (metadataError) {
82
+ if (cacheErrorCode(metadataError) === "ENOENT")
83
+ dirExists = "false";
84
+ // Keep inaccessible metadata unknown; diagnostics must not hide the cache error.
85
+ }
86
+ const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
87
+ const ownerMatches = typeof uid === "number" && typeof stats?.uid === "number"
88
+ ? String(stats.uid === uid)
89
+ : "unknown";
90
+ const dirMode = typeof stats?.mode === "number"
91
+ ? (stats.mode & 0o777).toString(8).padStart(3, "0")
92
+ : "unknown";
93
+ const dirHasUchg = directoryHasUchg(directory);
94
+ const rawErrno = cacheErrorCode(error);
95
+ const errno = typeof rawErrno === "string" && KNOWN_ERRNOS.has(rawErrno)
96
+ ? rawErrno
97
+ : "UNKNOWN";
98
+ const stateDirSource = env.HQ_STATE_DIR?.trim() ? "env" : "home";
99
+ return {
100
+ exception: originalCacheError(error),
101
+ tags: {
102
+ cache_op: operation,
103
+ cache_step: cacheError?.cacheStep ?? (operation === "read" ? "read" : "write_temp"),
104
+ errno,
105
+ state_dir_source: stateDirSource,
106
+ dir_exists: dirExists,
107
+ dir_owner_matches_uid: ownerMatches,
108
+ dir_mode: dirMode,
109
+ dir_has_uchg: dirHasUchg,
110
+ cache_path_kind: "<stateDir>/flags/<basename>",
111
+ },
112
+ };
113
+ }
6
114
  /** Keep the last server snapshot decision for this one startup-mode flag. */
7
115
  export function flagRegistryWorkerModeCachePath(stateDir, scopeIdentifiers) {
8
116
  const basename = scopeIdentifiers.length === 0
@@ -17,11 +125,11 @@ export function flagRegistryWorkerModeCachePath(stateDir, scopeIdentifiers) {
17
125
  export function readCachedFlagRegistryWorkerMode(filePath) {
18
126
  let latest = readCacheRecordIfPresent(filePath);
19
127
  const directory = path.dirname(filePath);
20
- if (!fs.existsSync(directory))
128
+ if (!withCacheFsStep("read", () => fs.existsSync(directory)))
21
129
  return latest;
22
130
  const basename = path.basename(filePath);
23
131
  const versionedPrefix = `${basename}.v`;
24
- for (const entry of fs.readdirSync(directory)) {
132
+ for (const entry of withCacheFsStep("readdir", () => fs.readdirSync(directory))) {
25
133
  if (!entry.startsWith(versionedPrefix) || !entry.endsWith(".json"))
26
134
  continue;
27
135
  const versionedPath = path.join(directory, entry);
@@ -32,7 +140,7 @@ export function readCachedFlagRegistryWorkerMode(filePath) {
32
140
  return latest;
33
141
  }
34
142
  export function writeCachedFlagRegistryWorkerMode(filePath, snapshot) {
35
- fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
143
+ withCacheFsStep("mkdir", () => fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 }));
36
144
  const existing = readCachedFlagRegistryWorkerMode(filePath);
37
145
  if (existing && snapshot.version < existing.version)
38
146
  return;
@@ -42,26 +150,27 @@ export function writeCachedFlagRegistryWorkerMode(filePath, snapshot) {
42
150
  const versionedPath = `${filePath}.v${snapshot.version}.json`;
43
151
  const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
44
152
  try {
45
- fs.writeFileSync(tempPath, `${JSON.stringify(snapshot)}\n`, {
153
+ withCacheFsStep("write_temp", () => fs.writeFileSync(tempPath, `${JSON.stringify(snapshot)}\n`, {
46
154
  encoding: "utf8",
47
155
  mode: 0o600,
48
156
  flag: "wx",
49
- });
157
+ }));
50
158
  try {
51
- fs.linkSync(tempPath, versionedPath);
159
+ withCacheFsStep("link", () => fs.linkSync(tempPath, versionedPath));
52
160
  }
53
161
  catch (error) {
54
162
  if (!isAlreadyExistsError(error))
55
163
  throw error;
56
164
  const existingVersion = readCacheRecord(versionedPath);
57
165
  if (existingVersion.value !== snapshot.value) {
58
- throw new Error("Conflicting flag-registry worker cache values share a version");
166
+ throw new FlagRegistryWorkerModeCacheStepError("link", new Error("Conflicting flag-registry worker cache values share a version"));
59
167
  }
60
168
  }
61
169
  }
62
170
  finally {
63
- if (fs.existsSync(tempPath))
64
- fs.unlinkSync(tempPath);
171
+ if (withCacheFsStep("unlink", () => fs.existsSync(tempPath))) {
172
+ withCacheFsStep("unlink", () => fs.unlinkSync(tempPath));
173
+ }
65
174
  }
66
175
  pruneOlderCacheRecords(filePath);
67
176
  }
@@ -76,7 +185,8 @@ function isCachedWorkerMode(value) {
76
185
  "value" in value && typeof value.value === "boolean";
77
186
  }
78
187
  function readCacheRecord(filePath) {
79
- const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
188
+ const contents = withCacheFsStep("read", () => fs.readFileSync(filePath, "utf8"));
189
+ const value = JSON.parse(contents);
80
190
  if (!isCachedWorkerMode(value)) {
81
191
  throw new Error("Cached flag-registry worker mode has an invalid shape");
82
192
  }
@@ -97,14 +207,14 @@ function pruneOlderCacheRecords(filePath) {
97
207
  const directory = path.dirname(filePath);
98
208
  const basename = path.basename(filePath);
99
209
  const versionedPrefix = `${basename}.v`;
100
- for (const entry of fs.readdirSync(directory)) {
210
+ for (const entry of withCacheFsStep("readdir", () => fs.readdirSync(directory))) {
101
211
  if (!entry.startsWith(versionedPrefix) || !entry.endsWith(".json"))
102
212
  continue;
103
213
  const recordPath = path.join(directory, entry);
104
214
  const record = readCacheRecordIfPresent(recordPath);
105
215
  if (record && record.version < latest.version) {
106
216
  try {
107
- fs.unlinkSync(recordPath);
217
+ withCacheFsStep("prune", () => fs.unlinkSync(recordPath));
108
218
  }
109
219
  catch (error) {
110
220
  if (!isNotFoundError(error))
@@ -112,11 +222,11 @@ function pruneOlderCacheRecords(filePath) {
112
222
  }
113
223
  }
114
224
  }
115
- if (fs.existsSync(filePath)) {
225
+ if (withCacheFsStep("prune", () => fs.existsSync(filePath))) {
116
226
  const legacyRecord = readCacheRecordIfPresent(filePath);
117
227
  if (legacyRecord && legacyRecord.version < latest.version) {
118
228
  try {
119
- fs.unlinkSync(filePath);
229
+ withCacheFsStep("prune", () => fs.unlinkSync(filePath));
120
230
  }
121
231
  catch (error) {
122
232
  if (!isNotFoundError(error))
@@ -136,11 +246,9 @@ function readCacheRecordIfPresent(filePath) {
136
246
  }
137
247
  }
138
248
  function isAlreadyExistsError(error) {
139
- return typeof error === "object" && error !== null &&
140
- "code" in error && error.code === "EEXIST";
249
+ return cacheErrorCode(error) === "EEXIST";
141
250
  }
142
251
  function isNotFoundError(error) {
143
- return typeof error === "object" && error !== null &&
144
- "code" in error && error.code === "ENOENT";
252
+ return cacheErrorCode(error) === "ENOENT";
145
253
  }
146
254
  //# sourceMappingURL=flag-registry-worker-mode-cache.js.map
@@ -14,7 +14,7 @@ import { workContextRoot } from "./work-context/paths.js";
14
14
  import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
15
15
  import { loadCachedTokens, resolveDefaultHqRoot } from "../utils/cognito-session.js";
16
16
  import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
17
- import { FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG, flagRegistryStateDir, flagRegistryWorkerModeCachePath, readCachedFlagRegistryWorkerMode, writeCachedFlagRegistryWorkerMode, } from "./flag-registry-worker-mode-cache.js";
17
+ import { FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG, flagRegistryWorkerModeCacheFailureContext, flagRegistryStateDir, flagRegistryWorkerModeCachePath, readCachedFlagRegistryWorkerMode, writeCachedFlagRegistryWorkerMode, } from "./flag-registry-worker-mode-cache.js";
18
18
  import { FLAG_REGISTRY_WORKER_SOURCE } from "./flag-registry-worker-source.generated.js";
19
19
  export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
20
20
  const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
@@ -140,8 +140,13 @@ function selfContainedWorkerEnabled(company) {
140
140
  return readCachedFlagRegistryWorkerMode(filePath)?.value ?? false;
141
141
  }
142
142
  catch (error) {
143
- Sentry.captureException(error, {
144
- tags: { component: "flag-registry-worker-mode-cache", worker: "flag-registry" },
143
+ const failure = flagRegistryWorkerModeCacheFailureContext(error, "read", filePath);
144
+ Sentry.captureException(failure.exception, {
145
+ tags: {
146
+ component: "flag-registry-worker-mode-cache",
147
+ worker: "flag-registry",
148
+ ...failure.tags,
149
+ },
145
150
  });
146
151
  return false;
147
152
  }
@@ -165,8 +170,14 @@ function cacheWorkerModeFromSnapshot(snapshot, company) {
165
170
  writeCachedFlagRegistryWorkerMode(flagRegistryWorkerModeCachePath(flagRegistryStateDir(), scopes), { version: snapshot.version, value: decision.value });
166
171
  }
167
172
  catch (error) {
168
- Sentry.captureException(error, {
169
- tags: { component: "flag-registry-worker-mode-cache", worker: "flag-registry" },
173
+ const filePath = flagRegistryWorkerModeCachePath(flagRegistryStateDir(), scopes);
174
+ const failure = flagRegistryWorkerModeCacheFailureContext(error, "write", filePath);
175
+ Sentry.captureException(failure.exception, {
176
+ tags: {
177
+ component: "flag-registry-worker-mode-cache",
178
+ worker: "flag-registry",
179
+ ...failure.tags,
180
+ },
170
181
  });
171
182
  }
172
183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.228.1",
3
+ "version": "5.228.2",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {