@indigoai-us/hq-cli 5.49.0 → 5.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * HQ CLI - Module management, package management, and cloud sync for HQ
4
4
  */
5
5
 
6
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="60f911e0-ddd5-5a2d-91c7-18ba47457b57")}catch(e){}}();
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d57c1cc3-6dd3-5c7f-8eef-c7692d41b836")}catch(e){}}();
7
7
  import { Command } from "commander";
8
8
  import { initSentry, Sentry } from "./sentry.js";
9
9
  import { registerAddCommand } from "./commands/add.js";
@@ -45,6 +45,7 @@ import { registerSignalsCommand } from "./commands/signals.js";
45
45
  import { registerReindexCommand } from "./commands/reindex.js";
46
46
  import { registerRescueCommand } from "./commands/rescue.js";
47
47
  import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
48
+ import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
48
49
  import { maybeWarnNewVersion, refreshVersionCache, } from "./utils/version-check.js";
49
50
  import { enforceVersionGate, shouldSkipGate, } from "./utils/version-gate.js";
50
51
  import { CLI_VERSION } from "./cli-version.js";
@@ -176,7 +177,18 @@ registerRescueCommand(program);
176
177
  await program.parseAsync();
177
178
  }
178
179
  catch (err) {
179
- Sentry.captureException(err);
180
+ // A full disk / exhausted quota / read-only filesystem is the user's
181
+ // machine, not an HQ code defect. Surface a clear, actionable message and
182
+ // skip Sentry capture so one full disk doesn't flood the tracker with
183
+ // identical, unfixable crash reports (HQ-CLI-2). Genuine errors still go
184
+ // to Sentry and still exit 1.
185
+ const envMsg = environmentalFsErrorMessage(err);
186
+ if (envMsg) {
187
+ process.stderr.write(`hq: ${envMsg}\n`);
188
+ }
189
+ else {
190
+ Sentry.captureException(err);
191
+ }
180
192
  process.exitCode = 1;
181
193
  }
182
194
  finally {
@@ -186,4 +198,4 @@ registerRescueCommand(program);
186
198
  }
187
199
  })();
188
200
  //# sourceMappingURL=index.js.map
189
- //# debugId=60f911e0-ddd5-5a2d-91c7-18ba47457b57
201
+ //# debugId=d57c1cc3-6dd3-5c7f-8eef-c7692d41b836
@@ -0,0 +1,10 @@
1
+ /**
2
+ * If `err` is an environmental disk/quota/read-only filesystem error, return a
3
+ * short, user-facing message explaining it; otherwise return `null`.
4
+ *
5
+ * A non-null result means the caller should print the message and SKIP Sentry
6
+ * capture — the condition is the user's machine, not a bug HQ can fix. A null
7
+ * result means "this is a normal error; handle it as usual (capture to Sentry)".
8
+ */
9
+ export declare function environmentalFsErrorMessage(err: unknown): string | null;
10
+ //# sourceMappingURL=environmental-error.d.ts.map
@@ -0,0 +1,40 @@
1
+ // src/utils/environmental-error.ts
2
+ //
3
+ // Classify errors that stem from the user's machine/filesystem state rather
4
+ // than an HQ code defect. These are unactionable from our side: a full disk, an
5
+ // exhausted quota, or a read-only filesystem. The CLI surfaces a clear,
6
+ // actionable message and does NOT report them to Sentry — otherwise a single
7
+ // full disk floods the issue tracker with identical, unfixable crash reports.
8
+ //
9
+ // HQ-CLI-2: `hq reindex` hit ENOSPC in the operation-lock temp-file write
10
+ // (`fs.openSync` → "ENOSPC: no space left on device, open") and the raw error
11
+ // propagated uncaught to the CLI's top-level handler, which captured it to
12
+ // Sentry and exited silently — 5 stack-trace crashes in 6 seconds, with no
13
+ // message telling the user their disk was full.
14
+ /**
15
+ * Node errno codes for "the filesystem cannot accept this write" — purely
16
+ * environmental, never a code bug. Mapped to the message shown to the user.
17
+ */
18
+
19
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="03976a9c-d4c3-57dc-bd1d-40c9d025193d")}catch(e){}}();
20
+ const ENVIRONMENTAL_FS_CODES = {
21
+ ENOSPC: "No space left on device. Free up disk space and try again.",
22
+ EDQUOT: "Disk quota exceeded. Free up space (or raise your quota) and try again.",
23
+ EROFS: "The filesystem is read-only, so HQ can't write here. Check the mount/permissions and try again.",
24
+ };
25
+ /**
26
+ * If `err` is an environmental disk/quota/read-only filesystem error, return a
27
+ * short, user-facing message explaining it; otherwise return `null`.
28
+ *
29
+ * A non-null result means the caller should print the message and SKIP Sentry
30
+ * capture — the condition is the user's machine, not a bug HQ can fix. A null
31
+ * result means "this is a normal error; handle it as usual (capture to Sentry)".
32
+ */
33
+ export function environmentalFsErrorMessage(err) {
34
+ const code = err?.code;
35
+ if (typeof code !== "string")
36
+ return null;
37
+ return ENVIRONMENTAL_FS_CODES[code] ?? null;
38
+ }
39
+ //# sourceMappingURL=environmental-error.js.map
40
+ //# debugId=03976a9c-d4c3-57dc-bd1d-40c9d025193d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.49.0",
3
+ "version": "5.50.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  "clean": "rm -rf dist"
19
19
  },
20
20
  "dependencies": {
21
- "@indigoai-us/hq-cloud": "^6.11.14",
21
+ "@indigoai-us/hq-cloud": "^6.12.0",
22
22
  "@indigoai-us/hq-onboarding": "^0.1.0",
23
23
  "@sentry/node": "^10.49.0",
24
24
  "chalk": "^5.3.0",
package/src/index.ts CHANGED
@@ -45,6 +45,7 @@ import { registerSignalsCommand } from "./commands/signals.js";
45
45
  import { registerReindexCommand } from "./commands/reindex.js";
46
46
  import { registerRescueCommand } from "./commands/rescue.js";
47
47
  import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
48
+ import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
48
49
  import {
49
50
  maybeWarnNewVersion,
50
51
  refreshVersionCache,
@@ -215,7 +216,17 @@ registerRescueCommand(program);
215
216
  }
216
217
  await program.parseAsync();
217
218
  } catch (err) {
218
- Sentry.captureException(err);
219
+ // A full disk / exhausted quota / read-only filesystem is the user's
220
+ // machine, not an HQ code defect. Surface a clear, actionable message and
221
+ // skip Sentry capture so one full disk doesn't flood the tracker with
222
+ // identical, unfixable crash reports (HQ-CLI-2). Genuine errors still go
223
+ // to Sentry and still exit 1.
224
+ const envMsg = environmentalFsErrorMessage(err);
225
+ if (envMsg) {
226
+ process.stderr.write(`hq: ${envMsg}\n`);
227
+ } else {
228
+ Sentry.captureException(err);
229
+ }
219
230
  process.exitCode = 1;
220
231
  } finally {
221
232
  // Release health: finalize the per-run session before the flush.
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { environmentalFsErrorMessage } from "./environmental-error.js";
3
+
4
+ /** Build a Node-style errno error with a `.code`, as fs.*Sync throws. */
5
+ function errnoError(code: string, message: string): NodeJS.ErrnoException {
6
+ const err = new Error(message) as NodeJS.ErrnoException;
7
+ err.code = code;
8
+ return err;
9
+ }
10
+
11
+ describe("environmentalFsErrorMessage", () => {
12
+ // HQ-CLI-2: the exact error that crashed `hq reindex` and flooded Sentry.
13
+ it("classifies ENOSPC (no space left on device) as environmental", () => {
14
+ const err = errnoError("ENOSPC", "ENOSPC: no space left on device, open '/x'");
15
+ const msg = environmentalFsErrorMessage(err);
16
+ expect(msg).not.toBeNull();
17
+ expect(msg).toMatch(/no space left on device/i);
18
+ });
19
+
20
+ it("classifies EDQUOT (quota exceeded) as environmental", () => {
21
+ const msg = environmentalFsErrorMessage(errnoError("EDQUOT", "EDQUOT: disk quota exceeded"));
22
+ expect(msg).toMatch(/quota/i);
23
+ });
24
+
25
+ it("classifies EROFS (read-only filesystem) as environmental", () => {
26
+ const msg = environmentalFsErrorMessage(errnoError("EROFS", "EROFS: read-only file system"));
27
+ expect(msg).toMatch(/read-only/i);
28
+ });
29
+
30
+ // A genuine code bug must still reach Sentry — only the disk-class codes are
31
+ // diverted, so we never silently swallow real defects.
32
+ it("returns null for a code-bug error (ENOENT) so it is still captured", () => {
33
+ expect(environmentalFsErrorMessage(errnoError("ENOENT", "ENOENT: not found"))).toBeNull();
34
+ });
35
+
36
+ it("returns null for a plain Error with no code", () => {
37
+ expect(environmentalFsErrorMessage(new Error("boom"))).toBeNull();
38
+ });
39
+
40
+ it("returns null for non-error values (null/undefined/string)", () => {
41
+ expect(environmentalFsErrorMessage(null)).toBeNull();
42
+ expect(environmentalFsErrorMessage(undefined)).toBeNull();
43
+ expect(environmentalFsErrorMessage("ENOSPC")).toBeNull();
44
+ });
45
+ });
@@ -0,0 +1,39 @@
1
+ // src/utils/environmental-error.ts
2
+ //
3
+ // Classify errors that stem from the user's machine/filesystem state rather
4
+ // than an HQ code defect. These are unactionable from our side: a full disk, an
5
+ // exhausted quota, or a read-only filesystem. The CLI surfaces a clear,
6
+ // actionable message and does NOT report them to Sentry — otherwise a single
7
+ // full disk floods the issue tracker with identical, unfixable crash reports.
8
+ //
9
+ // HQ-CLI-2: `hq reindex` hit ENOSPC in the operation-lock temp-file write
10
+ // (`fs.openSync` → "ENOSPC: no space left on device, open") and the raw error
11
+ // propagated uncaught to the CLI's top-level handler, which captured it to
12
+ // Sentry and exited silently — 5 stack-trace crashes in 6 seconds, with no
13
+ // message telling the user their disk was full.
14
+
15
+ /**
16
+ * Node errno codes for "the filesystem cannot accept this write" — purely
17
+ * environmental, never a code bug. Mapped to the message shown to the user.
18
+ */
19
+ const ENVIRONMENTAL_FS_CODES: Record<string, string> = {
20
+ ENOSPC: "No space left on device. Free up disk space and try again.",
21
+ EDQUOT:
22
+ "Disk quota exceeded. Free up space (or raise your quota) and try again.",
23
+ EROFS:
24
+ "The filesystem is read-only, so HQ can't write here. Check the mount/permissions and try again.",
25
+ };
26
+
27
+ /**
28
+ * If `err` is an environmental disk/quota/read-only filesystem error, return a
29
+ * short, user-facing message explaining it; otherwise return `null`.
30
+ *
31
+ * A non-null result means the caller should print the message and SKIP Sentry
32
+ * capture — the condition is the user's machine, not a bug HQ can fix. A null
33
+ * result means "this is a normal error; handle it as usual (capture to Sentry)".
34
+ */
35
+ export function environmentalFsErrorMessage(err: unknown): string | null {
36
+ const code = (err as NodeJS.ErrnoException | null | undefined)?.code;
37
+ if (typeof code !== "string") return null;
38
+ return ENVIRONMENTAL_FS_CODES[code] ?? null;
39
+ }