@indigoai-us/hq-cli 5.179.1 → 5.181.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/CHANGELOG.md +13 -0
- package/dist/commands/core-checkpoint.d.ts +5 -1
- package/dist/commands/core-checkpoint.js +56 -1
- package/dist/commands/daemon.js +1 -1
- package/dist/lib/daemon/catalog.d.ts +7 -4
- package/dist/lib/daemon/catalog.js +15 -6
- package/dist/lib/daemon/hq-cloud.d.ts +3 -1
- package/dist/lib/daemon/hq-cloud.js +6 -2
- package/dist/lib/daemon/run.js +2 -1
- package/dist/main.js +7 -0
- package/dist/utils/checkpoint-stamp-write-error.d.ts +28 -0
- package/dist/utils/checkpoint-stamp-write-error.js +99 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.181.0] — 2026-09-24
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- `hq daemon` runs the session host from the hq-cloud copy it keeps up to date (hq-cloud's `hq-outpost-session-host`, when that copy has it), the same way it already runs sync and the outpost heartbeat, and restarts it after an hq-cloud update. Session-host fixes, such as starting on an Outpost that signs in with machine credentials, now reach boxes without an hq CLI release.
|
|
10
|
+
|
|
11
|
+
## [5.180.0] — 2026-09-24
|
|
12
|
+
|
|
5
13
|
## [5.179.1] — 2026-09-24
|
|
6
14
|
|
|
7
15
|
### Added
|
|
@@ -13,6 +21,11 @@
|
|
|
13
21
|
person's inbox while the answer they need still reaches them. `hq dm inbox`
|
|
14
22
|
and `hq dm thread` mark agent-audience messages with an `[agent]` tag.
|
|
15
23
|
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Windows checkpoint stamps now use atomic replacement and bounded retries for
|
|
27
|
+
transient filesystem errors, so a brief lock does not abort the checkpoint.
|
|
28
|
+
|
|
16
29
|
## [5.179.0] — 2026-09-24
|
|
17
30
|
|
|
18
31
|
### Fixed
|
|
@@ -48,7 +48,11 @@ export type WriteStampsOptions = {
|
|
|
48
48
|
* permission errno so the HqStateWriteError conversion can be proven without a
|
|
49
49
|
* real unwritable path.
|
|
50
50
|
*/
|
|
51
|
-
fs?: Pick<typeof fs, "mkdirSync" | "writeFileSync">;
|
|
51
|
+
fs?: Pick<typeof fs, "mkdirSync" | "writeFileSync" | "renameSync" | "unlinkSync">;
|
|
52
|
+
/** Runtime seam for proving the Windows-only atomic path without host assumptions. */
|
|
53
|
+
platform?: NodeJS.Platform;
|
|
54
|
+
/** Synchronous delay seam for the bounded Windows lock retry. */
|
|
55
|
+
waitForRetry?: (delayMs: number) => void;
|
|
52
56
|
};
|
|
53
57
|
export declare function writeStamps(liveRoot: string, sessionId: string | undefined, options?: WriteStampsOptions): string[];
|
|
54
58
|
/**
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
11
12
|
import { execFileSync, spawn } from "node:child_process";
|
|
12
13
|
import { homedir } from "node:os";
|
|
13
14
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
14
15
|
import { peekIdToken } from "../utils/id-token.js";
|
|
15
16
|
import { toHqStateWriteError } from "../utils/hq-state-write-error.js";
|
|
17
|
+
import { checkpointStampWriteErrorFor, isCheckpointStampWriteError, } from "../utils/checkpoint-stamp-write-error.js";
|
|
16
18
|
const DEFAULT_TRIGGER = "stop-gate";
|
|
17
19
|
const BACKENDS = new Set(["auto", "claude", "codex", "grok", "none"]);
|
|
18
20
|
// Pinned by operator directive 2026-09-15; change defaults here deliberately.
|
|
@@ -408,14 +410,65 @@ function writeHqState(targetPath, operation, perform) {
|
|
|
408
410
|
perform();
|
|
409
411
|
}
|
|
410
412
|
catch (error) {
|
|
413
|
+
if (isCheckpointStampWriteError(error))
|
|
414
|
+
throw error;
|
|
411
415
|
const typed = toHqStateWriteError(error, targetPath, operation);
|
|
412
416
|
if (typed)
|
|
413
417
|
throw typed;
|
|
414
418
|
throw error;
|
|
415
419
|
}
|
|
416
420
|
}
|
|
421
|
+
const WINDOWS_CHECKPOINT_WRITE_RETRY_CODES = new Set(["EBUSY", "EPERM", "UNKNOWN"]);
|
|
422
|
+
const WINDOWS_CHECKPOINT_WRITE_RETRY_DELAYS_MS = [25, 50, 100];
|
|
423
|
+
function waitForCheckpointWriteRetry(delayMs) {
|
|
424
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)), 0, 0, delayMs);
|
|
425
|
+
}
|
|
426
|
+
function removeCheckpointStampTemp(tempPath, fsImpl, waitForRetry) {
|
|
427
|
+
for (let attempt = 0;; attempt += 1) {
|
|
428
|
+
try {
|
|
429
|
+
fsImpl.unlinkSync(tempPath);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
const code = error?.code;
|
|
434
|
+
if (code === "ENOENT")
|
|
435
|
+
return;
|
|
436
|
+
if (typeof code === "string" &&
|
|
437
|
+
WINDOWS_CHECKPOINT_WRITE_RETRY_CODES.has(code) &&
|
|
438
|
+
attempt < WINDOWS_CHECKPOINT_WRITE_RETRY_DELAYS_MS.length) {
|
|
439
|
+
waitForRetry(WINDOWS_CHECKPOINT_WRITE_RETRY_DELAYS_MS[attempt]);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
throw checkpointStampWriteErrorFor(error, "unlink") ?? error;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function writeCheckpointStampAtomically(targetPath, contents, fsImpl, waitForRetry) {
|
|
447
|
+
for (let attempt = 0;; attempt += 1) {
|
|
448
|
+
const tempPath = path.join(path.dirname(targetPath), `.hq-checkpoint-stamp.tmp.${process.pid}.${randomUUID()}`);
|
|
449
|
+
let failedOperation = "open";
|
|
450
|
+
try {
|
|
451
|
+
fsImpl.writeFileSync(tempPath, contents, { flag: "wx" });
|
|
452
|
+
failedOperation = "rename";
|
|
453
|
+
fsImpl.renameSync(tempPath, targetPath);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
removeCheckpointStampTemp(tempPath, fsImpl, waitForRetry);
|
|
458
|
+
const code = error?.code;
|
|
459
|
+
if (typeof code !== "string" ||
|
|
460
|
+
!WINDOWS_CHECKPOINT_WRITE_RETRY_CODES.has(code) ||
|
|
461
|
+
attempt >= WINDOWS_CHECKPOINT_WRITE_RETRY_DELAYS_MS.length) {
|
|
462
|
+
throw checkpointStampWriteErrorFor(error, failedOperation) ?? error;
|
|
463
|
+
}
|
|
464
|
+
waitForRetry(WINDOWS_CHECKPOINT_WRITE_RETRY_DELAYS_MS[attempt]);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
417
468
|
export function writeStamps(liveRoot, sessionId, options = {}) {
|
|
418
469
|
const fsImpl = options.fs ?? fs;
|
|
470
|
+
const platform = options.platform ?? process.platform;
|
|
471
|
+
const waitForRetry = options.waitForRetry ?? waitForCheckpointWriteRetry;
|
|
419
472
|
const stateDir = path.join(liveRoot, "workspace", "orchestrator", "hook-state");
|
|
420
473
|
writeHqState(stateDir, "create the checkpoint state directory", () => fsImpl.mkdirSync(stateDir, { recursive: true }));
|
|
421
474
|
const sessionKey = sessionId
|
|
@@ -427,7 +480,9 @@ export function writeStamps(liveRoot, sessionId, options = {}) {
|
|
|
427
480
|
path.join(stateDir, `checkpoint-cli-last-${sessionKey}`),
|
|
428
481
|
];
|
|
429
482
|
for (const stampPath of stampPaths) {
|
|
430
|
-
writeHqState(stampPath, "write its checkpoint state file", () =>
|
|
483
|
+
writeHqState(stampPath, "write its checkpoint state file", () => platform === "win32"
|
|
484
|
+
? writeCheckpointStampAtomically(stampPath, timestamp, fsImpl, waitForRetry)
|
|
485
|
+
: fsImpl.writeFileSync(stampPath, timestamp));
|
|
431
486
|
}
|
|
432
487
|
return stampPaths;
|
|
433
488
|
}
|
package/dist/commands/daemon.js
CHANGED
|
@@ -432,7 +432,7 @@ async function runServiceSelfUpdate(d) {
|
|
|
432
432
|
updateCli: cliSelfUpdatable() ? () => daemonSelfUpdate({ env }) : null,
|
|
433
433
|
tools: TOOL_NAMES.map((name) => hostToolUpdater(name, env, d.home)),
|
|
434
434
|
updateHqCloud: async () => updateManagedHqCloud(paths.root, env),
|
|
435
|
-
hqCloudUnits: hqCloudUnits(detected.type),
|
|
435
|
+
hqCloudUnits: hqCloudUnits(detected.type, enabledServices(detected.type, readDaemonConfig(paths.configPath))),
|
|
436
436
|
requestDaemonRestart: () => writeControlRequest(paths.requestsDir, { action: "restart-daemon" }),
|
|
437
437
|
requestRestart: (unit) => writeControlRequest(paths.requestsDir, { action: "restart", unit }),
|
|
438
438
|
log: d.out,
|
|
@@ -64,6 +64,8 @@ export interface PlanContext {
|
|
|
64
64
|
syncRunnerPath: string | null;
|
|
65
65
|
/** hq-cloud's `hq-outpost-heartbeat` bin, when the hq-cloud in use has one. */
|
|
66
66
|
heartbeatRunnerPath: string | null;
|
|
67
|
+
/** hq-cloud's `hq-outpost-session-host` bin, when the hq-cloud in use has one. */
|
|
68
|
+
sessionHostRunnerPath: string | null;
|
|
67
69
|
signedIn: boolean;
|
|
68
70
|
/** ~/.hq-agent/kit.json exists. */
|
|
69
71
|
kitConfigured: boolean;
|
|
@@ -89,11 +91,12 @@ export interface PlanContext {
|
|
|
89
91
|
codexRunnerScript: string | null;
|
|
90
92
|
}
|
|
91
93
|
/**
|
|
92
|
-
* The units that run hq-cloud's code on
|
|
93
|
-
*
|
|
94
|
-
*
|
|
94
|
+
* The units that run hq-cloud's code on this host, restarted after the updater
|
|
95
|
+
* installs a new hq-cloud. Only outposts and self-hosted machines run the
|
|
96
|
+
* outpost heartbeat (agent machines run the agent kit's own); the session host
|
|
97
|
+
* is included wherever it is enabled.
|
|
95
98
|
*/
|
|
96
|
-
export declare function hqCloudUnits(machineType: MachineType): ServiceName[];
|
|
99
|
+
export declare function hqCloudUnits(machineType: MachineType, enabled: readonly ServiceName[]): ServiceName[];
|
|
97
100
|
/** Written by outpost provisioning; the daemon runs it rather than re-implementing Codex login and relay setup. */
|
|
98
101
|
export declare const CODEX_RUNNER_SCRIPT = "/usr/local/bin/outpost-codex-runner.sh";
|
|
99
102
|
/** The outpost's Claude runner script, read for the session name it uses. */
|
|
@@ -97,12 +97,18 @@ function firstBlock(checks) {
|
|
|
97
97
|
return hit ? { state: "waiting", reason: hit[1] } : undefined;
|
|
98
98
|
}
|
|
99
99
|
/**
|
|
100
|
-
* The units that run hq-cloud's code on
|
|
101
|
-
*
|
|
102
|
-
*
|
|
100
|
+
* The units that run hq-cloud's code on this host, restarted after the updater
|
|
101
|
+
* installs a new hq-cloud. Only outposts and self-hosted machines run the
|
|
102
|
+
* outpost heartbeat (agent machines run the agent kit's own); the session host
|
|
103
|
+
* is included wherever it is enabled.
|
|
103
104
|
*/
|
|
104
|
-
export function hqCloudUnits(machineType) {
|
|
105
|
-
|
|
105
|
+
export function hqCloudUnits(machineType, enabled) {
|
|
106
|
+
const units = ["sync"];
|
|
107
|
+
if (machineType === "outpost" || machineType === "self-hosted")
|
|
108
|
+
units.push("heartbeat");
|
|
109
|
+
if (enabled.includes("session-host"))
|
|
110
|
+
units.push("session-host");
|
|
111
|
+
return units;
|
|
106
112
|
}
|
|
107
113
|
/** Written by outpost provisioning; the daemon runs it rather than re-implementing Codex login and relay setup. */
|
|
108
114
|
export const CODEX_RUNNER_SCRIPT = "/usr/local/bin/outpost-codex-runner.sh";
|
|
@@ -181,7 +187,10 @@ function planService(service, ctx) {
|
|
|
181
187
|
case "session-host":
|
|
182
188
|
return [
|
|
183
189
|
base(service, {
|
|
184
|
-
|
|
190
|
+
// hq-cloud's own session host picks up hq-cloud updates without a CLI release.
|
|
191
|
+
command: ctx.sessionHostRunnerPath
|
|
192
|
+
? { type: "node", script: ctx.sessionHostRunnerPath, args: [] }
|
|
193
|
+
: { type: "hq", args: ["outposts", "session-host"] },
|
|
185
194
|
env: ctx.hqRoot ? { HQ_ROOT: ctx.hqRoot } : {},
|
|
186
195
|
blocked: firstBlock([[!ctx.signedIn, NOT_SIGNED_IN]]),
|
|
187
196
|
}),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which hq-cloud the daemon runs sync
|
|
2
|
+
* Which hq-cloud the daemon runs sync, the outpost heartbeat and the session host from.
|
|
3
3
|
*
|
|
4
4
|
* The hq CLI ships with a copy of hq-cloud, but hq-cloud releases more often
|
|
5
5
|
* than the CLI. The updater keeps a newer copy of the same major version in
|
|
@@ -27,6 +27,8 @@ export interface HqCloudRunners {
|
|
|
27
27
|
syncRunner: string | null;
|
|
28
28
|
/** `hq-outpost-heartbeat`, which older hq-cloud releases do not have. */
|
|
29
29
|
heartbeat: string | null;
|
|
30
|
+
/** `hq-outpost-session-host`, which older hq-cloud releases do not have. */
|
|
31
|
+
sessionHost: string | null;
|
|
30
32
|
}
|
|
31
33
|
export declare function hqCloudRunners(root: string, exists?: (p: string) => boolean): HqCloudRunners;
|
|
32
34
|
/** Install the newest release of the bundled major version, never older than the bundled copy. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which hq-cloud the daemon runs sync
|
|
2
|
+
* Which hq-cloud the daemon runs sync, the outpost heartbeat and the session host from.
|
|
3
3
|
*
|
|
4
4
|
* The hq CLI ships with a copy of hq-cloud, but hq-cloud releases more often
|
|
5
5
|
* than the CLI. The updater keeps a newer copy of the same major version in
|
|
@@ -48,7 +48,11 @@ export function hqCloudRunners(root, exists = fs.existsSync) {
|
|
|
48
48
|
const p = path.join(root, "dist", "bin", name);
|
|
49
49
|
return exists(p) ? p : null;
|
|
50
50
|
};
|
|
51
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
syncRunner: bin("sync-runner.js"),
|
|
53
|
+
heartbeat: bin("outpost-heartbeat.js"),
|
|
54
|
+
sessionHost: bin("outpost-session-host.js"),
|
|
55
|
+
};
|
|
52
56
|
}
|
|
53
57
|
/** Install the newest release of the bundled major version, never older than the bundled copy. */
|
|
54
58
|
export function hqCloudInstallCommand(prefix, bundledVersion, platform = process.platform) {
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -249,7 +249,7 @@ export async function runDaemon(opts) {
|
|
|
249
249
|
const hqRoot = configured && fs.existsSync(configured) ? configured : undefined;
|
|
250
250
|
// Re-picked every reconcile, so a newer hq-cloud from the updater is used as soon as it lands.
|
|
251
251
|
const hqCloud = currentHqCloud(paths.root);
|
|
252
|
-
const runners = hqCloud ? hqCloudRunners(hqCloud.root) : { syncRunner: null, heartbeat: null };
|
|
252
|
+
const runners = hqCloud ? hqCloudRunners(hqCloud.root) : { syncRunner: null, heartbeat: null, sessionHost: null };
|
|
253
253
|
return planUnits(enabledServices(machineType, config), {
|
|
254
254
|
machineType,
|
|
255
255
|
platform,
|
|
@@ -257,6 +257,7 @@ export async function runDaemon(opts) {
|
|
|
257
257
|
hqRoot,
|
|
258
258
|
syncRunnerPath: runners.syncRunner,
|
|
259
259
|
heartbeatRunnerPath: runners.heartbeat,
|
|
260
|
+
sessionHostRunnerPath: runners.sessionHost,
|
|
260
261
|
signedIn: signedIn(env, home, log),
|
|
261
262
|
kitConfigured: fs.existsSync(agentKitPaths(home, env).kitConfigPath),
|
|
262
263
|
desktopAppManagesSync: machineType === "desktop" &&
|
package/dist/main.js
CHANGED
|
@@ -25,6 +25,7 @@ import { qmdQueryDocumentMessage } from "./utils/qmd-query-document-error.js";
|
|
|
25
25
|
import { qmdModelDownloadMessage } from "./utils/qmd-model-download-error.js";
|
|
26
26
|
import { qmdWorkdirMissingMessage } from "./utils/qmd-workdir-missing-error.js";
|
|
27
27
|
import { hqStateWriteErrorMessage } from "./utils/hq-state-write-error.js";
|
|
28
|
+
import { checkpointStampWriteCaptureContext, checkpointStampWriteMessage, } from "./utils/checkpoint-stamp-write-error.js";
|
|
28
29
|
import { incompleteInstallMessage, incompleteInstallCaptureContext, } from "./utils/incomplete-install-error.js";
|
|
29
30
|
import { isExpectedUserError } from "./utils/expected-cli-error.js";
|
|
30
31
|
import { isVarlockEnvError } from "./run/env-graph-guard.js";
|
|
@@ -734,6 +735,8 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
734
735
|
// caller-owned conditions. Keep them at error level and attach bounded,
|
|
735
736
|
// installation-independent context so OneDrive/antivirus/path-length
|
|
736
737
|
// hypotheses can be separated without creating one issue per host path.
|
|
738
|
+
const checkpointStampContext = checkpointStampWriteCaptureContext(err);
|
|
739
|
+
const checkpointStampMessage = checkpointStampWriteMessage(err);
|
|
737
740
|
const fsContext = filesystemErrorCaptureContext(err);
|
|
738
741
|
const fsMsg = fsContext ? filesystemErrorMessage(err) : null;
|
|
739
742
|
if (qmdMsg) {
|
|
@@ -781,6 +784,10 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
781
784
|
else if (transportMsg) {
|
|
782
785
|
deps.stderr.write(`hq: ${transportMsg}\n`);
|
|
783
786
|
}
|
|
787
|
+
else if (checkpointStampContext && checkpointStampMessage) {
|
|
788
|
+
deps.sentry.captureException(err, checkpointStampContext);
|
|
789
|
+
deps.stderr.write(`hq: ${checkpointStampMessage}\n`);
|
|
790
|
+
}
|
|
784
791
|
else if (fsMsg && fsContext) {
|
|
785
792
|
deps.sentry.captureException(err, fsContext);
|
|
786
793
|
deps.stderr.write(`hq: ${fsMsg}\n`);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type CheckpointStampFsOperation = "open" | "write" | "rename" | "unlink";
|
|
2
|
+
type CheckpointStampErrnoCode = "EBUSY" | "EPERM" | "UNKNOWN";
|
|
3
|
+
type CheckpointStampWriteDiagnostics = {
|
|
4
|
+
fs_op: CheckpointStampFsOperation;
|
|
5
|
+
target_kind: "checkpoint_stamp";
|
|
6
|
+
errno_code: CheckpointStampErrnoCode;
|
|
7
|
+
platform: "win32";
|
|
8
|
+
};
|
|
9
|
+
export type CheckpointStampWriteCaptureContext = {
|
|
10
|
+
fingerprint: ["fs-write-failed", "checkpoint_stamp", CheckpointStampErrnoCode];
|
|
11
|
+
tags: CheckpointStampWriteDiagnostics;
|
|
12
|
+
};
|
|
13
|
+
export declare class CheckpointStampWriteError extends Error {
|
|
14
|
+
name: string;
|
|
15
|
+
readonly code: CheckpointStampErrnoCode;
|
|
16
|
+
readonly syscall: CheckpointStampFsOperation;
|
|
17
|
+
readonly checkpointStampWriteDiagnostics: CheckpointStampWriteDiagnostics;
|
|
18
|
+
constructor(diagnostics: CheckpointStampWriteDiagnostics, originalStack?: string);
|
|
19
|
+
}
|
|
20
|
+
/** Wrap only the captured Windows lock/error classes, keeping the original frames and dropping paths. */
|
|
21
|
+
export declare function checkpointStampWriteErrorFor(error: unknown, fallbackOperation: CheckpointStampFsOperation): CheckpointStampWriteError | null;
|
|
22
|
+
export declare function isCheckpointStampWriteError(error: unknown): boolean;
|
|
23
|
+
/** Value-free report context for the Windows checkpoint stamp write boundary. */
|
|
24
|
+
export declare function checkpointStampWriteCaptureContext(error: unknown): CheckpointStampWriteCaptureContext | null;
|
|
25
|
+
/** User-facing text stays actionable without including the HQ or home path. */
|
|
26
|
+
export declare function checkpointStampWriteMessage(error: unknown): string | null;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=checkpoint-stamp-write-error.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const CHECKPOINT_STAMP_ERRNO_CODES = new Set([
|
|
2
|
+
"EBUSY",
|
|
3
|
+
"EPERM",
|
|
4
|
+
"UNKNOWN",
|
|
5
|
+
]);
|
|
6
|
+
const CHECKPOINT_STAMP_FS_OPERATIONS = new Set([
|
|
7
|
+
"open",
|
|
8
|
+
"write",
|
|
9
|
+
"rename",
|
|
10
|
+
"unlink",
|
|
11
|
+
]);
|
|
12
|
+
function errorLike(value) {
|
|
13
|
+
return value !== null && typeof value === "object" ? value : null;
|
|
14
|
+
}
|
|
15
|
+
function isCheckpointStampErrnoCode(value) {
|
|
16
|
+
return typeof value === "string" && CHECKPOINT_STAMP_ERRNO_CODES.has(value);
|
|
17
|
+
}
|
|
18
|
+
function isCheckpointStampFsOperation(value) {
|
|
19
|
+
return (typeof value === "string" &&
|
|
20
|
+
CHECKPOINT_STAMP_FS_OPERATIONS.has(value));
|
|
21
|
+
}
|
|
22
|
+
export class CheckpointStampWriteError extends Error {
|
|
23
|
+
name = "CheckpointStampWriteError";
|
|
24
|
+
code;
|
|
25
|
+
syscall;
|
|
26
|
+
checkpointStampWriteDiagnostics;
|
|
27
|
+
constructor(diagnostics, originalStack) {
|
|
28
|
+
super(`checkpoint stamp write failed during ${diagnostics.fs_op} (${diagnostics.errno_code})`);
|
|
29
|
+
this.code = diagnostics.errno_code;
|
|
30
|
+
this.syscall = diagnostics.fs_op;
|
|
31
|
+
this.checkpointStampWriteDiagnostics = diagnostics;
|
|
32
|
+
if (originalStack) {
|
|
33
|
+
const frames = originalStack.split("\n").slice(1);
|
|
34
|
+
if (frames.length > 0)
|
|
35
|
+
this.stack = `${this.name}: ${this.message}\n${frames.join("\n")}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Wrap only the captured Windows lock/error classes, keeping the original frames and dropping paths. */
|
|
40
|
+
export function checkpointStampWriteErrorFor(error, fallbackOperation) {
|
|
41
|
+
const record = errorLike(error);
|
|
42
|
+
if (!record || !isCheckpointStampErrnoCode(record.code))
|
|
43
|
+
return null;
|
|
44
|
+
const fsOperation = isCheckpointStampFsOperation(record.syscall)
|
|
45
|
+
? record.syscall
|
|
46
|
+
: fallbackOperation;
|
|
47
|
+
const diagnostics = {
|
|
48
|
+
fs_op: fsOperation,
|
|
49
|
+
target_kind: "checkpoint_stamp",
|
|
50
|
+
errno_code: record.code,
|
|
51
|
+
platform: "win32",
|
|
52
|
+
};
|
|
53
|
+
return new CheckpointStampWriteError(diagnostics, error instanceof Error ? error.stack : undefined);
|
|
54
|
+
}
|
|
55
|
+
function diagnosticsFor(error) {
|
|
56
|
+
const record = errorLike(error);
|
|
57
|
+
if (!record || record.name !== "CheckpointStampWriteError")
|
|
58
|
+
return null;
|
|
59
|
+
const diagnostics = error
|
|
60
|
+
.checkpointStampWriteDiagnostics;
|
|
61
|
+
if (diagnostics === null || typeof diagnostics !== "object")
|
|
62
|
+
return null;
|
|
63
|
+
const candidate = diagnostics;
|
|
64
|
+
if (!isCheckpointStampFsOperation(candidate.fs_op) ||
|
|
65
|
+
candidate.target_kind !== "checkpoint_stamp" ||
|
|
66
|
+
!isCheckpointStampErrnoCode(candidate.errno_code) ||
|
|
67
|
+
candidate.platform !== "win32") {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
fs_op: candidate.fs_op,
|
|
72
|
+
target_kind: "checkpoint_stamp",
|
|
73
|
+
errno_code: candidate.errno_code,
|
|
74
|
+
platform: "win32",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function isCheckpointStampWriteError(error) {
|
|
78
|
+
return diagnosticsFor(error) !== null;
|
|
79
|
+
}
|
|
80
|
+
/** Value-free report context for the Windows checkpoint stamp write boundary. */
|
|
81
|
+
export function checkpointStampWriteCaptureContext(error) {
|
|
82
|
+
const tags = diagnosticsFor(error);
|
|
83
|
+
if (!tags)
|
|
84
|
+
return null;
|
|
85
|
+
return {
|
|
86
|
+
// This is a grouping key. Keep it independent of installation paths.
|
|
87
|
+
fingerprint: ["fs-write-failed", tags.target_kind, tags.errno_code],
|
|
88
|
+
tags,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** User-facing text stays actionable without including the HQ or home path. */
|
|
92
|
+
export function checkpointStampWriteMessage(error) {
|
|
93
|
+
const diagnostics = diagnosticsFor(error);
|
|
94
|
+
if (!diagnostics)
|
|
95
|
+
return null;
|
|
96
|
+
return (`hq could not write a checkpoint state file (${diagnostics.errno_code} during ${diagnostics.fs_op}). ` +
|
|
97
|
+
"Check the checkpoint state folder and retry.");
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=checkpoint-stamp-write-error.js.map
|