@indigoai-us/hq-cli 5.103.18 → 5.103.20
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,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.103.20] — 2026-08-24
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- The bundled sync engine now requires `@indigoai-us/hq-cloud` 6.15.37, the
|
|
10
|
+
current production release.
|
|
11
|
+
|
|
12
|
+
## [5.103.19] — 2026-08-22
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `hq search` (and its `--mode semantic`/`hybrid` siblings) no longer files a
|
|
17
|
+
Sentry crash when the qmd child is stopped by the host — for example when the
|
|
18
|
+
out-of-memory killer stops a CPU-only embedding run on a small box, or an
|
|
19
|
+
operator interrupts the search. The spawn seam now keeps the terminating
|
|
20
|
+
signal instead of discarding it, so such a run is reported to the operator as
|
|
21
|
+
an actionable, signal-named line (tailored to whether a search or an index
|
|
22
|
+
build was killed) and is no longer captured as an hq-cli defect. Because the
|
|
23
|
+
synthesized message no longer interpolates the caller's query, it can no longer
|
|
24
|
+
mint a brand-new permanent Sentry issue per distinct query. Crash-class signals
|
|
25
|
+
(SIGSEGV/SIGABRT/SIGBUS), which usually indicate a genuine native fault, and
|
|
26
|
+
every non-zero qmd exit are unchanged and still reported. (Sentry 7677702704)
|
|
27
|
+
|
|
5
28
|
## [5.103.18] — 2026-08-22
|
|
6
29
|
|
|
7
30
|
### Fixed
|
|
@@ -3,6 +3,13 @@ export type QmdProcessResult = {
|
|
|
3
3
|
stdout: string;
|
|
4
4
|
stderr: string;
|
|
5
5
|
error?: Error;
|
|
6
|
+
/**
|
|
7
|
+
* The signal that terminated the child, when qmd was KILLED rather than
|
|
8
|
+
* exited (spawnSync then reports `status: null`, `signal: <name>`). OPTIONAL
|
|
9
|
+
* so every existing runner and test double keeps compiling unchanged; read by
|
|
10
|
+
* finishRunQmd to classify a signalled qmd (HQ-CLI 7677702704).
|
|
11
|
+
*/
|
|
12
|
+
signal?: NodeJS.Signals | string | null;
|
|
6
13
|
};
|
|
7
14
|
export type QmdProcessRunner = (bin: string, args: string[], options: {
|
|
8
15
|
cwd?: string;
|
|
@@ -43,6 +50,7 @@ export type QmdSpawn = (cmd: string, args: string[], options: {
|
|
|
43
50
|
stdout?: string;
|
|
44
51
|
stderr?: string;
|
|
45
52
|
error?: Error;
|
|
53
|
+
signal?: NodeJS.Signals | string | null;
|
|
46
54
|
};
|
|
47
55
|
/**
|
|
48
56
|
* Prefix `execDir` onto the child env PATH so a node-entry launcher can re-spawn
|
|
@@ -79,6 +87,27 @@ export declare class QmdCollectionExistsError extends QmdExitError {
|
|
|
79
87
|
name: string;
|
|
80
88
|
constructor(message: string, args: string[], status: number | null, stdout: string, stderr: string, collectionName: string);
|
|
81
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* qmd did NOT exit — it was KILLED BY A SIGNAL before it finished (spawnSync
|
|
92
|
+
* reports `status: null` with a non-null `signal`). hq-cli passes qmd no timeout
|
|
93
|
+
* and never signals the child itself, so the signal always originates OUTSIDE
|
|
94
|
+
* hq-cli: most often the host OOM killer during CPU-only embedding on a small
|
|
95
|
+
* box, or an operator interrupt (Ctrl-C, a terminated shell). `signal` carries
|
|
96
|
+
* the signal name so the top-level handler can name it and branch its remedy.
|
|
97
|
+
*
|
|
98
|
+
* Before this class a signalled qmd rendered as `exited with an unknown status:
|
|
99
|
+
* <qmd's raw stderr>` — and because that stderr was often purely advisory (an
|
|
100
|
+
* embeddings tip, a GPU warning) and the message interpolated the caller's whole
|
|
101
|
+
* argv, every distinct search query minted a brand-new permanent Sentry issue
|
|
102
|
+
* (HQ-CLI 7677702704 — the same unbounded-fingerprint failure fixed for
|
|
103
|
+
* HQ-CLI-S). This typed subclass lets the boundary classify the condition as the
|
|
104
|
+
* caller's environment and stop fingerprinting on the query text.
|
|
105
|
+
*/
|
|
106
|
+
export declare class QmdTerminatedError extends QmdExitError {
|
|
107
|
+
readonly signal: string;
|
|
108
|
+
name: string;
|
|
109
|
+
constructor(message: string, args: string[], status: number | null, stdout: string, stderr: string, signal: string);
|
|
110
|
+
}
|
|
82
111
|
export type ResolveQmdBinOptions = {
|
|
83
112
|
env?: Record<string, string | undefined>;
|
|
84
113
|
isExecutable?: (candidate: string) => boolean;
|
|
@@ -5,6 +5,7 @@ import * as os from 'node:os';
|
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { isQmdNativeBindingError } from '../../utils/qmd-native-binding-error.js';
|
|
8
|
+
import { redactErrorText } from '../../utils/redact-error-text.js';
|
|
8
9
|
import { planCommandSpawn } from '../../utils/windows-spawn.js';
|
|
9
10
|
const require = createRequire(import.meta.url);
|
|
10
11
|
/** The `path` implementation for a (possibly injected) platform. */
|
|
@@ -61,6 +62,30 @@ export class QmdCollectionExistsError extends QmdExitError {
|
|
|
61
62
|
this.collectionName = collectionName;
|
|
62
63
|
}
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* qmd did NOT exit — it was KILLED BY A SIGNAL before it finished (spawnSync
|
|
67
|
+
* reports `status: null` with a non-null `signal`). hq-cli passes qmd no timeout
|
|
68
|
+
* and never signals the child itself, so the signal always originates OUTSIDE
|
|
69
|
+
* hq-cli: most often the host OOM killer during CPU-only embedding on a small
|
|
70
|
+
* box, or an operator interrupt (Ctrl-C, a terminated shell). `signal` carries
|
|
71
|
+
* the signal name so the top-level handler can name it and branch its remedy.
|
|
72
|
+
*
|
|
73
|
+
* Before this class a signalled qmd rendered as `exited with an unknown status:
|
|
74
|
+
* <qmd's raw stderr>` — and because that stderr was often purely advisory (an
|
|
75
|
+
* embeddings tip, a GPU warning) and the message interpolated the caller's whole
|
|
76
|
+
* argv, every distinct search query minted a brand-new permanent Sentry issue
|
|
77
|
+
* (HQ-CLI 7677702704 — the same unbounded-fingerprint failure fixed for
|
|
78
|
+
* HQ-CLI-S). This typed subclass lets the boundary classify the condition as the
|
|
79
|
+
* caller's environment and stop fingerprinting on the query text.
|
|
80
|
+
*/
|
|
81
|
+
export class QmdTerminatedError extends QmdExitError {
|
|
82
|
+
signal;
|
|
83
|
+
name = 'QmdTerminatedError';
|
|
84
|
+
constructor(message, args, status, stdout, stderr, signal) {
|
|
85
|
+
super(message, args, status, stdout, stderr);
|
|
86
|
+
this.signal = signal;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
64
89
|
function isExecutable(candidate) {
|
|
65
90
|
try {
|
|
66
91
|
fs.accessSync(candidate, fs.constants.X_OK);
|
|
@@ -231,6 +256,9 @@ const defaultSpawn = (cmd, args, options) => {
|
|
|
231
256
|
stdout: result.stdout ?? undefined,
|
|
232
257
|
stderr: result.stderr ?? undefined,
|
|
233
258
|
error: result.error,
|
|
259
|
+
// Carry the signal that killed the child (status null) so finishRunQmd can
|
|
260
|
+
// classify a signalled qmd instead of erasing the cause (HQ-CLI 7677702704).
|
|
261
|
+
signal: result.signal,
|
|
234
262
|
};
|
|
235
263
|
};
|
|
236
264
|
/**
|
|
@@ -644,6 +672,32 @@ export function resolveQmdInvocation(options = {}) {
|
|
|
644
672
|
return bundled;
|
|
645
673
|
throw new QmdBinaryMissingError(`Unable to resolve qmd. Probed ${probes.join('; ')}. Install @tobilu/qmd or set HQ_QMD_BIN to an executable qmd binary.`);
|
|
646
674
|
}
|
|
675
|
+
/** ANSI CSI escape sequences qmd may leave on its streams (SGR colour, a trailing show-cursor control). */
|
|
676
|
+
const ANSI_ESCAPE = new RegExp(`${String.fromCharCode(0x1b)}\\[[0-9;?]*[ -/]*[@-~]`, 'g');
|
|
677
|
+
/** Lines qmd prints as ADVISORIES (a tip, a warning) — never a failure reason. */
|
|
678
|
+
const QMD_ADVISORY_LINE = /^(?:Tip:|Warning:|QMD Warning:)/i;
|
|
679
|
+
/**
|
|
680
|
+
* Render qmd's captured output as a human diagnostic for a SIGNALLED run: strip
|
|
681
|
+
* ANSI/control noise, then drop purely-advisory lines as long as a substantive
|
|
682
|
+
* line survives. If ONLY advisory lines were printed — the reported shape, where
|
|
683
|
+
* an embeddings tip and a GPU warning were all qmd emitted before it was killed —
|
|
684
|
+
* say so explicitly rather than presenting an advisory as the cause of death.
|
|
685
|
+
* Any retained qmd text is redacted + length-bounded, so a hostile line can
|
|
686
|
+
* neither forge a second `hq:` line, emit terminal escapes, nor leak a token.
|
|
687
|
+
*/
|
|
688
|
+
function describeQmdTermination(stdout, stderr) {
|
|
689
|
+
const lines = `${stderr}\n${stdout}`
|
|
690
|
+
.split(/\r?\n/)
|
|
691
|
+
.map((line) => line.replace(ANSI_ESCAPE, '').trim())
|
|
692
|
+
.filter((line) => line.length > 0);
|
|
693
|
+
if (lines.length === 0)
|
|
694
|
+
return '';
|
|
695
|
+
const substantive = lines.filter((line) => !QMD_ADVISORY_LINE.test(line));
|
|
696
|
+
if (substantive.length === 0)
|
|
697
|
+
return 'qmd printed only advisory output before it was terminated';
|
|
698
|
+
const detail = redactErrorText(substantive.join(' '));
|
|
699
|
+
return detail ? `qmd reported: ${detail}` : '';
|
|
700
|
+
}
|
|
647
701
|
/** Normalise a spawn result and raise the typed qmd failures. */
|
|
648
702
|
function finishRunQmd(result, bin, args) {
|
|
649
703
|
const normalized = {
|
|
@@ -651,12 +705,29 @@ function finishRunQmd(result, bin, args) {
|
|
|
651
705
|
stdout: result.stdout ?? '',
|
|
652
706
|
stderr: result.stderr ?? '',
|
|
653
707
|
error: result.error,
|
|
708
|
+
signal: result.signal,
|
|
654
709
|
};
|
|
655
710
|
if (normalized.error) {
|
|
656
711
|
throw new QmdBinaryMissingError(`Unable to execute qmd at ${bin}: ${normalized.error.message}`);
|
|
657
712
|
}
|
|
658
713
|
if (normalized.status === 0)
|
|
659
714
|
return normalized;
|
|
715
|
+
// qmd did not EXIT — it was KILLED BY A SIGNAL before it finished (spawnSync:
|
|
716
|
+
// status null, signal set). hq-cli passes qmd no timeout and never signals the
|
|
717
|
+
// child, so the signal came from OUTSIDE hq-cli — most often the host OOM
|
|
718
|
+
// killer during CPU-only embedding on a small box, or an operator interrupt.
|
|
719
|
+
// Name the signal and the SUBCOMMAND only, never args.join(' '), so the Sentry
|
|
720
|
+
// group can no longer fingerprint per search query (HQ-CLI 7677702704).
|
|
721
|
+
if (normalized.status === null && normalized.signal) {
|
|
722
|
+
const signal = String(normalized.signal);
|
|
723
|
+
const subcommand = typeof args[0] === 'string' && args[0].length > 0 ? redactErrorText(args[0]) || 'qmd' : 'qmd';
|
|
724
|
+
const namedSignal = redactErrorText(signal) || 'an unknown signal';
|
|
725
|
+
const context = describeQmdTermination(normalized.stdout, normalized.stderr);
|
|
726
|
+
const signalMessage = context
|
|
727
|
+
? `qmd ${subcommand} was terminated by signal ${namedSignal} before it finished (${context})`
|
|
728
|
+
: `qmd ${subcommand} was terminated by signal ${namedSignal} before it finished`;
|
|
729
|
+
throw new QmdTerminatedError(signalMessage, args, normalized.status, normalized.stdout, normalized.stderr, signal);
|
|
730
|
+
}
|
|
660
731
|
const detail = normalized.stderr || normalized.stdout || 'qmd returned no diagnostic output';
|
|
661
732
|
const message = `qmd ${args.join(' ')} exited with ${normalized.status ?? 'an unknown status'}: ${detail}`;
|
|
662
733
|
if (/(?:collection|qmd:\/\/).*(?:not found|does not exist|unknown)|(?:not found|does not exist).*collection/i.test(detail)) {
|
package/dist/main.js
CHANGED
|
@@ -67,6 +67,7 @@ import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
|
|
|
67
67
|
import { networkTransportErrorMessage } from "./utils/network-transport-error.js";
|
|
68
68
|
import { qmdNativeBindingErrorMessage } from "./utils/qmd-native-binding-error.js";
|
|
69
69
|
import { qmdMissingCollectionMessage } from "./utils/qmd-collection-missing-error.js";
|
|
70
|
+
import { qmdTerminatedMessage } from "./utils/qmd-terminated-error.js";
|
|
70
71
|
import { isExpectedUserError } from "./utils/expected-cli-error.js";
|
|
71
72
|
import { isEpipe } from "./utils/epipe.js";
|
|
72
73
|
import { isInterceptedProcessExit } from "./utils/intercepted-process-exit.js";
|
|
@@ -457,7 +458,20 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
457
458
|
// raised by hq's OWN reconciliation (`collection add`/`context add`) names
|
|
458
459
|
// a collection hq built itself, so it stays a captured internal error.
|
|
459
460
|
const collectionMsg = qmdMsg ? null : qmdMissingCollectionMessage(err);
|
|
460
|
-
|
|
461
|
+
// A qmd child KILLED BY A SIGNAL (spawnSync status null + signal) is the
|
|
462
|
+
// caller's HOST, not an hq-cli defect: hq-cli passes qmd no timeout and
|
|
463
|
+
// never signals the child, so the signal came from outside — most often the
|
|
464
|
+
// OOM killer during CPU-only embedding on a small box, or an operator
|
|
465
|
+
// interrupt. Before this branch the discarded signal rendered as `exited
|
|
466
|
+
// with an unknown status: <qmd's raw advisory stderr>`, and because that
|
|
467
|
+
// message embedded the caller's whole argv, each distinct query minted a
|
|
468
|
+
// brand-new permanent issue (HQ-CLI 7677702704, the HQ-CLI-S failure mode).
|
|
469
|
+
// finishRunQmd now types it QmdTerminatedError; print the signal-named
|
|
470
|
+
// remedy and skip capture. Evaluated AFTER the native-binding and
|
|
471
|
+
// collection-missing checks (both narrower typed signatures) and BEFORE the
|
|
472
|
+
// environmental / transport / generic branches.
|
|
473
|
+
const terminatedMsg = qmdMsg || collectionMsg ? null : qmdTerminatedMessage(err);
|
|
474
|
+
const envMsg = qmdMsg || collectionMsg || terminatedMsg ? null : environmentalFsErrorMessage(err);
|
|
461
475
|
// A raw network transport failure (undici's `TypeError: fetch failed`
|
|
462
476
|
// with a ConnectTimeoutError / ECONNREFUSED / ENOTFOUND cause) is the
|
|
463
477
|
// caller's connectivity, not an hq-cli defect. Before this branch it fell
|
|
@@ -468,13 +482,16 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
468
482
|
// message that names the unreachable host, exit 1, and skip Sentry.
|
|
469
483
|
// Ordered after the environmental check so a full disk keeps its exact
|
|
470
484
|
// existing message.
|
|
471
|
-
const transportMsg = qmdMsg || collectionMsg || envMsg ? null : networkTransportErrorMessage(err);
|
|
485
|
+
const transportMsg = qmdMsg || collectionMsg || terminatedMsg || envMsg ? null : networkTransportErrorMessage(err);
|
|
472
486
|
if (qmdMsg) {
|
|
473
487
|
deps.stderr.write(`hq: ${qmdMsg}\n`);
|
|
474
488
|
}
|
|
475
489
|
else if (collectionMsg) {
|
|
476
490
|
deps.stderr.write(`hq: ${collectionMsg}\n`);
|
|
477
491
|
}
|
|
492
|
+
else if (terminatedMsg) {
|
|
493
|
+
deps.stderr.write(`hq: ${terminatedMsg}\n`);
|
|
494
|
+
}
|
|
478
495
|
else if (envMsg) {
|
|
479
496
|
deps.stderr.write(`hq: ${envMsg}\n`);
|
|
480
497
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* If `err` is a qmd child terminated by a genuinely EXTERNAL signal (the host
|
|
3
|
+
* OOM killer or an operator interrupt), return an actionable, signal-named
|
|
4
|
+
* remedy tailored to what qmd was doing; otherwise return `null`. A null result
|
|
5
|
+
* covers non-QmdTerminatedError values AND crash-class/unrecognised signals,
|
|
6
|
+
* which must stay on the captured-error path. Mirrors qmdNativeBindingErrorMessage
|
|
7
|
+
* / qmdMissingCollectionMessage so the top-level handler branches the same way:
|
|
8
|
+
* non-null means print-and-skip-Sentry, null means "handle as usual (capture)".
|
|
9
|
+
*/
|
|
10
|
+
export declare function qmdTerminatedMessage(err: unknown): string | null;
|
|
11
|
+
//# sourceMappingURL=qmd-terminated-error.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/utils/qmd-terminated-error.ts
|
|
2
|
+
//
|
|
3
|
+
// Classify a qmd child that was KILLED BY A SIGNAL — not one that exited
|
|
4
|
+
// non-zero — and decide whether it is the caller's HOST ENVIRONMENT (print an
|
|
5
|
+
// actionable remedy, SKIP Sentry) or a genuine qmd/native CRASH (leave it on the
|
|
6
|
+
// captured-error path). Sibling of qmd-native-binding-error.ts (HQ-CLI-J),
|
|
7
|
+
// qmd-collection-missing-error.ts (HQ-CLI-S), environmental-error.ts (HQ-CLI-2)
|
|
8
|
+
// and network-transport-error.ts (HQ-CLI-G).
|
|
9
|
+
//
|
|
10
|
+
// HQ-CLI 7677702704: `hq search --mode semantic ...` spawned qmd, which was
|
|
11
|
+
// KILLED BY A SIGNAL before it finished. The reported profile — a 2 GB / 2-vCPU
|
|
12
|
+
// box, qmd warning it had no GPU and was building embeddings on CPU, ~12 minutes
|
|
13
|
+
// of runtime before the throw — is the shape of an out-of-memory kill (the Linux
|
|
14
|
+
// OOM killer sends SIGKILL). The spawn seam discarded spawnSync's `signal`, so
|
|
15
|
+
// the failure rendered as `exited with an unknown status: <qmd's raw advisory
|
|
16
|
+
// stderr>`; because that synthesized message interpolated the caller's whole
|
|
17
|
+
// argv, every distinct query minted a brand-new permanent issue — the same
|
|
18
|
+
// unbounded-fingerprint failure fixed for HQ-CLI-S. finishRunQmd now types the
|
|
19
|
+
// condition QmdTerminatedError; this classifier closes the boundary.
|
|
20
|
+
//
|
|
21
|
+
// Which signals are the caller's environment (and skip Sentry):
|
|
22
|
+
// - SIGKILL: the process cannot catch it, so it is always sent from OUTSIDE —
|
|
23
|
+
// almost always the host OOM killer here.
|
|
24
|
+
// - SIGINT / SIGTERM / SIGHUP / SIGQUIT: an operator or a terminated shell
|
|
25
|
+
// interrupting the run.
|
|
26
|
+
// CRASH-class signals — SIGSEGV / SIGABRT / SIGBUS — are deliberately NOT
|
|
27
|
+
// carved out: they usually mean qmd's OWN native code faulted (a genuine
|
|
28
|
+
// bundled-qmd defect), so a swallow would hide a real regression AND mislead the
|
|
29
|
+
// user with a memory remedy. For those (and any unrecognised or missing signal)
|
|
30
|
+
// this returns null so the boundary reports them to Sentry. That stays safe:
|
|
31
|
+
// finishRunQmd's message names the signal and subcommand ONLY (never the query),
|
|
32
|
+
// so a captured crash still groups per signal, not per query.
|
|
33
|
+
//
|
|
34
|
+
// The gate is the TYPED CLASS (`name === 'QmdTerminatedError'`) plus a signal
|
|
35
|
+
// allow-list — never qmd's advisory wording ('Tip:', 'needs embeddings'), which
|
|
36
|
+
// this fix's reproduction shows also rides a SUCCESSFUL exit-0 run. hq-cli passes
|
|
37
|
+
// qmd no timeout and never signals the child, so a carved-out signal always
|
|
38
|
+
// originates outside hq-cli. The only value rendered into the remedy is the
|
|
39
|
+
// signal name, drawn from the allow-list of safe constants and passed through
|
|
40
|
+
// redactErrorText defensively; the caller's query and the raw subcommand are
|
|
41
|
+
// never echoed.
|
|
42
|
+
import { redactErrorText } from "./redact-error-text.js";
|
|
43
|
+
/** SIGKILL: uncatchable, so always sent from outside — here, the host OOM killer. */
|
|
44
|
+
const HOST_KILL = "SIGKILL";
|
|
45
|
+
/** Signals that mean an operator or shell INTERRUPTED qmd before it finished. */
|
|
46
|
+
const INTERRUPTS = new Set(["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"]);
|
|
47
|
+
/** qmd subcommands that are caller-supplied SEARCH reads (mirrors search.ts). */
|
|
48
|
+
const SEARCH_READS = new Set(["search", "vsearch", "query", "get"]);
|
|
49
|
+
/**
|
|
50
|
+
* If `err` is a qmd child terminated by a genuinely EXTERNAL signal (the host
|
|
51
|
+
* OOM killer or an operator interrupt), return an actionable, signal-named
|
|
52
|
+
* remedy tailored to what qmd was doing; otherwise return `null`. A null result
|
|
53
|
+
* covers non-QmdTerminatedError values AND crash-class/unrecognised signals,
|
|
54
|
+
* which must stay on the captured-error path. Mirrors qmdNativeBindingErrorMessage
|
|
55
|
+
* / qmdMissingCollectionMessage so the top-level handler branches the same way:
|
|
56
|
+
* non-null means print-and-skip-Sentry, null means "handle as usual (capture)".
|
|
57
|
+
*/
|
|
58
|
+
export function qmdTerminatedMessage(err) {
|
|
59
|
+
if (err === null || typeof err !== "object")
|
|
60
|
+
return null;
|
|
61
|
+
const record = err;
|
|
62
|
+
if (record.name !== "QmdTerminatedError")
|
|
63
|
+
return null;
|
|
64
|
+
const rawSignal = typeof record.signal === "string" ? record.signal : "";
|
|
65
|
+
const isHostKill = rawSignal === HOST_KILL;
|
|
66
|
+
const isInterrupt = INTERRUPTS.has(rawSignal);
|
|
67
|
+
// Crash-class (SIGSEGV/SIGABRT/SIGBUS) and any unrecognised/missing signal are
|
|
68
|
+
// left to the captured-error path — they are likely a real qmd/native defect.
|
|
69
|
+
if (!isHostKill && !isInterrupt)
|
|
70
|
+
return null;
|
|
71
|
+
// rawSignal is now one of a fixed allow-list, so it is safe to name; redact
|
|
72
|
+
// defensively to match the sibling classifiers' discipline.
|
|
73
|
+
const signal = redactErrorText(rawSignal) || "an unknown signal";
|
|
74
|
+
if (isInterrupt) {
|
|
75
|
+
return (`qmd was interrupted (signal ${signal}) before it finished, so the command ` +
|
|
76
|
+
`returned no results. Re-run it when the machine is free.`);
|
|
77
|
+
}
|
|
78
|
+
// SIGKILL: the host reclaimed qmd — most often the OOM killer during CPU-only
|
|
79
|
+
// embedding on a small box. Tailor the remedy to what qmd was doing so an
|
|
80
|
+
// index-build kill is not told to re-run the very command that was killed or
|
|
81
|
+
// offered a search-only `--mode keyword` that does not apply to it.
|
|
82
|
+
const args = record.args;
|
|
83
|
+
const subcommand = Array.isArray(args) && typeof args[0] === "string" ? args[0] : "";
|
|
84
|
+
if (SEARCH_READS.has(subcommand)) {
|
|
85
|
+
return (`The local search was stopped by the host (signal ${signal}) before qmd finished — ` +
|
|
86
|
+
`most often the machine ran out of memory while building embeddings on CPU. Run ` +
|
|
87
|
+
`'hq index sync' once (ideally on a host with more memory) to build embeddings up ` +
|
|
88
|
+
`front, retry with '--mode keyword' (which needs far less memory), or run the ` +
|
|
89
|
+
`search on a host with more free memory.`);
|
|
90
|
+
}
|
|
91
|
+
return (`qmd was stopped by the host (signal ${signal}) before it finished — most often the ` +
|
|
92
|
+
`machine ran out of memory (building a large index on CPU is memory-heavy). Re-run it ` +
|
|
93
|
+
`on a host with more free memory, or index fewer collections at a time.`);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=qmd-terminated-error.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.103.
|
|
3
|
+
"version": "5.103.20",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
32
32
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
33
|
-
"@indigoai-us/hq-cloud": "~6.15.
|
|
33
|
+
"@indigoai-us/hq-cloud": "~6.15.37",
|
|
34
34
|
"@indigoai-us/hq-onboarding": "^0.1.0",
|
|
35
35
|
"@sentry/node": "^10.49.0",
|
|
36
36
|
"@tobilu/qmd": "2.5.3",
|