@indigoai-us/hq-cli 5.201.2 → 5.201.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 +6 -0
- package/dist/lib/flag-registry.js +14 -6
- package/dist/sentry.js +31 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.201.3] — 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- A flag-registry worker whose startup fails because the working directory was deleted or its package files were being replaced no longer creates a Sentry issue. Other flag-registry worker errors remain visible with a worker tag.
|
|
10
|
+
|
|
5
11
|
## [5.201.2] — 2026-09-25
|
|
6
12
|
|
|
7
13
|
### Changed
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* keep a short-lived, offline CLI alive until the client's network deadline.
|
|
7
7
|
*/
|
|
8
8
|
import { Worker } from "node:worker_threads";
|
|
9
|
+
import * as Sentry from "@sentry/node";
|
|
9
10
|
import { isFlagSnapshot, readLocalOverride, resolveFlag, } from "@indigoai-us/hq-flags-client";
|
|
10
11
|
import { readMachineCompanyBinding } from "./work-context/company.js";
|
|
11
12
|
import { readSessionState } from "./work-context/state.js";
|
|
@@ -116,12 +117,19 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
|
|
|
116
117
|
const companyIdentifiers = [companyUid, company.companySlug].filter((value) => Boolean(value));
|
|
117
118
|
processReader = processSnapshotReader(companyIdentifiers);
|
|
118
119
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
// The SDK's child-process integration captures every Worker error unless
|
|
121
|
+
// its process-wide captureWorkerErrors option is disabled. Scope only this
|
|
122
|
+
// worker so the reporting boundary can classify its startup errors without
|
|
123
|
+
// changing capture for unrelated workers.
|
|
124
|
+
const worker = Sentry.withScope((scope) => {
|
|
125
|
+
scope.setTag("worker", "flag-registry");
|
|
126
|
+
return (dependencies.createWorker ?? ((filename, options) => new Worker(filename, options)))(new URL("./flag-registry-worker.js", import.meta.url), {
|
|
127
|
+
workerData: {
|
|
128
|
+
endpoint,
|
|
129
|
+
...(companyUid ? { companyUid } : {}),
|
|
130
|
+
...(company.companySlug ? { companyIdentifiers: [company.companySlug] } : {}),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
125
133
|
});
|
|
126
134
|
worker.on("message", (value) => {
|
|
127
135
|
if (isFlagSnapshot(value))
|
package/dist/sentry.js
CHANGED
|
@@ -20,7 +20,14 @@ import { installTreeTornSentryTagsFor, sentryFingerprintFor, sentryMcpCollisionC
|
|
|
20
20
|
* Consistent with the swallow-EPIPE posture established in #138.
|
|
21
21
|
*/
|
|
22
22
|
export function epipeAwareBeforeSend(event, hint) {
|
|
23
|
-
|
|
23
|
+
const flagRegistryWorkerEvent = isFlagRegistryWorkerEvent(event);
|
|
24
|
+
if (flagRegistryWorkerEvent) {
|
|
25
|
+
if (isExpectedFlagRegistryWorkerStartupFailure(hint?.originalException))
|
|
26
|
+
return null;
|
|
27
|
+
event.level = "warning";
|
|
28
|
+
event.tags = { ...event.tags, worker: "flag-registry" };
|
|
29
|
+
}
|
|
30
|
+
if (!flagRegistryWorkerEvent && isEpipe(hint?.originalException))
|
|
24
31
|
return null;
|
|
25
32
|
// Path-independent belt for environmental filesystem failures (ENOSPC /
|
|
26
33
|
// EDQUOT / EROFS — a full disk, exhausted quota, or read-only mount, e.g.
|
|
@@ -31,7 +38,7 @@ export function epipeAwareBeforeSend(event, hint) {
|
|
|
31
38
|
// either. handleTopLevelError already prints an actionable message for the
|
|
32
39
|
// boundary route; dropping the event here suppresses the fatal regardless of
|
|
33
40
|
// route, while the CLI still exits non-zero. HQ-CLI-R (Sentry 7671416365).
|
|
34
|
-
if (environmentalFsErrorMessage(hint?.originalException))
|
|
41
|
+
if (!flagRegistryWorkerEvent && environmentalFsErrorMessage(hint?.originalException))
|
|
35
42
|
return null;
|
|
36
43
|
// Path-independent belt for an IN-PROCESS incomplete-install module-load
|
|
37
44
|
// failure — hq-cli's own globally installed tree is not intact at load time
|
|
@@ -50,7 +57,7 @@ export function epipeAwareBeforeSend(event, hint) {
|
|
|
50
57
|
// node_modules, so an hq-cli packaging fault (a dist/ miss, or a bare miss the
|
|
51
58
|
// requirer does NOT declare — a possible undeclared/peer dependency) and the
|
|
52
59
|
// path-less unattributable shape are NOT dropped here and stay captured.
|
|
53
|
-
if (incompleteInstallMessage(hint?.originalException))
|
|
60
|
+
if (!flagRegistryWorkerEvent && incompleteInstallMessage(hint?.originalException))
|
|
54
61
|
return null;
|
|
55
62
|
// An incomplete-install shape that is NOT suppressed (a bare miss the requirer
|
|
56
63
|
// does not declare, or a path-less loader/resolver ENOENT) should still arrive
|
|
@@ -95,6 +102,27 @@ export function epipeAwareBeforeSend(event, hint) {
|
|
|
95
102
|
}
|
|
96
103
|
return beforeSend(event, hint);
|
|
97
104
|
}
|
|
105
|
+
function isFlagRegistryWorkerEvent(event) {
|
|
106
|
+
return event.tags?.worker === "flag-registry" && Boolean(event.exception?.values?.some((value) => value.mechanism?.type === "auto.child_process.worker_thread"));
|
|
107
|
+
}
|
|
108
|
+
function isExpectedFlagRegistryWorkerStartupFailure(error) {
|
|
109
|
+
if (typeof error !== "object" || error === null)
|
|
110
|
+
return false;
|
|
111
|
+
const details = error;
|
|
112
|
+
if (details.code === "ENOENT" && details.syscall === "uv_cwd") {
|
|
113
|
+
const stack = typeof details.stack === "string" ? details.stack : "";
|
|
114
|
+
return stack.includes("node:internal/main/worker_thread") && /\bat process\.cwd \(/.test(stack);
|
|
115
|
+
}
|
|
116
|
+
if (details.code !== "MODULE_NOT_FOUND" && details.code !== "ERR_MODULE_NOT_FOUND") {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const message = typeof details.message === "string" ? details.message : "";
|
|
120
|
+
const missingModule = /Cannot find module\s+['"]([^'"]+)['"]/.exec(message)?.[1];
|
|
121
|
+
if (!missingModule)
|
|
122
|
+
return false;
|
|
123
|
+
return missingModule.replaceAll("\\", "/").endsWith("/flag-registry-worker.js") ||
|
|
124
|
+
missingModule === "flag-registry-worker.js";
|
|
125
|
+
}
|
|
98
126
|
/**
|
|
99
127
|
* Tests substitute only the transport so the real SDK lifecycle can be
|
|
100
128
|
* exercised without an ingest request. Production callers pass nothing.
|