@indigoai-us/hq-cli 5.158.0 → 5.159.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
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.159.0] — 2026-09-24
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Hook timeout reports now identify the slow child hook and group concurrent
|
|
10
|
+
session-wide slowness together. Update checks have bounded network calls, and
|
|
11
|
+
the core-write guard avoids repeated subprocess work.
|
|
12
|
+
- Machine-wide hook warnings and exceeded timeouts now keep separate Sentry
|
|
13
|
+
groups, and per-script rate limits no longer suppress a later machine-wide
|
|
14
|
+
report from the same session.
|
|
15
|
+
|
|
5
16
|
## [5.158.0] — 2026-09-24
|
|
6
17
|
|
|
7
18
|
### Added
|
|
@@ -27,7 +27,7 @@ export declare const MAX_SENTRY_REPORT_METADATA_VALUE_BYTES = 256;
|
|
|
27
27
|
* is owner-approved telemetry for the exact hook script and may include a
|
|
28
28
|
* company directory; it is never a substitute for hook input or a transcript.
|
|
29
29
|
*/
|
|
30
|
-
export declare const ALLOWED_SENTRY_REPORT_METADATA_KEYS: readonly ["hook_name", "hook_path", "hook_event", "hook_script", "tool_name", "session_id", "declared_timeout_ms", "elapsed_ms", "remaining_ms", "watchdog_timeout_ms", "hook_timeout_ms", "hq_version", "platform", "load_average", "exit_code", "final_elapsed_ms", "bash_env_set", "cwd_kind", "shell", "nproc", "hook_sequence"];
|
|
30
|
+
export declare const ALLOWED_SENTRY_REPORT_METADATA_KEYS: readonly ["hook_name", "hook_path", "hook_event", "hook_script", "tool_name", "session_id", "declared_timeout_ms", "elapsed_ms", "remaining_ms", "watchdog_timeout_ms", "hook_timeout_ms", "hq_version", "platform", "load_average", "spawn_ms", "slow_child", "slow_child_ms", "exit_code", "final_elapsed_ms", "bash_env_set", "cwd_kind", "shell", "nproc", "hook_sequence"];
|
|
31
31
|
export declare const MAX_SENTRY_REPORT_METADATA_KEYS: number;
|
|
32
32
|
export type HookSequenceEntry = {
|
|
33
33
|
script: string;
|
|
@@ -45,11 +45,26 @@ const HOOK_TIMEOUT_WARNING_METRIC = "hq.hook_timeout_warning";
|
|
|
45
45
|
const HOOK_TIMEOUT_EXCEEDED_METRIC = "hq.hook_timeout_exceeded";
|
|
46
46
|
const HOOK_TIMEOUT_EXCEEDED_FINGERPRINT = "hook-timeout-exceeded";
|
|
47
47
|
const HOOK_TIMEOUT_WARNING_FINGERPRINT = "hook-timeout";
|
|
48
|
+
const HOOK_TIMEOUT_MACHINE_WIDE_FINGERPRINT = "hq-hook-slow";
|
|
48
49
|
const MAX_HOOK_SEQUENCE_ENTRIES = 20;
|
|
49
50
|
const HOOK_TIMEOUT_RATE_LIMIT_WINDOW_MS = 10 * 60 * 1_000;
|
|
50
51
|
const RATE_LIMIT_LOCK_WAIT_MS = 500;
|
|
51
52
|
const RATE_LIMIT_LOCK_POLL_MS = 5;
|
|
52
53
|
const RATE_LIMIT_LOCK_STALE_MS = 5_000;
|
|
54
|
+
const KNOWN_HOOK_SCRIPTS = new Set([
|
|
55
|
+
"master-hook.sh",
|
|
56
|
+
"hook-gate.sh",
|
|
57
|
+
"hook-timeout-watchdog.sh",
|
|
58
|
+
"check-hq-update.sh",
|
|
59
|
+
"block-core-writes-bash.sh",
|
|
60
|
+
"block-core-writes.sh",
|
|
61
|
+
"block-hq-worktree-session.sh",
|
|
62
|
+
"inject-policy-on-trigger.sh",
|
|
63
|
+
"inject-local-context.sh",
|
|
64
|
+
"session-title.sh",
|
|
65
|
+
"45-lanes-senior-monitor.sh",
|
|
66
|
+
"lanes-senior-monitor-stop-gate.sh",
|
|
67
|
+
]);
|
|
53
68
|
/**
|
|
54
69
|
* The only hook diagnostics that can be attached as metadata. The list omits
|
|
55
70
|
* command text, prompts, payloads, company names, and repositories. `hook_path`
|
|
@@ -71,6 +86,9 @@ export const ALLOWED_SENTRY_REPORT_METADATA_KEYS = [
|
|
|
71
86
|
"hq_version",
|
|
72
87
|
"platform",
|
|
73
88
|
"load_average",
|
|
89
|
+
"spawn_ms",
|
|
90
|
+
"slow_child",
|
|
91
|
+
"slow_child_ms",
|
|
74
92
|
"exit_code",
|
|
75
93
|
"final_elapsed_ms",
|
|
76
94
|
"bash_env_set",
|
|
@@ -223,6 +241,9 @@ export function parseSentryReportEvent(stdin) {
|
|
|
223
241
|
? boundedHookSequence(rawMetadata[key])
|
|
224
242
|
: rawMetadata[key];
|
|
225
243
|
}
|
|
244
|
+
if (typeof metadata.slow_child === "string") {
|
|
245
|
+
metadata.slow_child = safeSlowChild(metadata);
|
|
246
|
+
}
|
|
226
247
|
return {
|
|
227
248
|
code: 0,
|
|
228
249
|
event: {
|
|
@@ -311,6 +332,63 @@ function loadBucket(loadAverage) {
|
|
|
311
332
|
return "10-20";
|
|
312
333
|
return ">=20";
|
|
313
334
|
}
|
|
335
|
+
function parsedLoadAverage(metadata) {
|
|
336
|
+
const value = metadata.load_average;
|
|
337
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0)
|
|
338
|
+
return value;
|
|
339
|
+
if (typeof value !== "string")
|
|
340
|
+
return undefined;
|
|
341
|
+
if (value === "unavailable")
|
|
342
|
+
return "unavailable";
|
|
343
|
+
const match = /^\s*\{?\s*([0-9]+(?:\.[0-9]+)?)/.exec(value);
|
|
344
|
+
if (!match)
|
|
345
|
+
return undefined;
|
|
346
|
+
const parsed = Number(match[1]);
|
|
347
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
348
|
+
}
|
|
349
|
+
function operatingSystemName(metadata) {
|
|
350
|
+
const platform = stringMetadata(metadata, "platform", "").toLowerCase();
|
|
351
|
+
if (/windows|win32|mingw|msys|cygwin/.test(platform))
|
|
352
|
+
return "windows";
|
|
353
|
+
if (/darwin|macos|mac os/.test(platform))
|
|
354
|
+
return "macos";
|
|
355
|
+
if (/linux/.test(platform))
|
|
356
|
+
return "linux";
|
|
357
|
+
return "other";
|
|
358
|
+
}
|
|
359
|
+
function spawnMsBucket(metadata) {
|
|
360
|
+
const value = metadata.spawn_ms;
|
|
361
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
362
|
+
return "unavailable";
|
|
363
|
+
if (value < 100)
|
|
364
|
+
return "<100ms";
|
|
365
|
+
if (value < 500)
|
|
366
|
+
return "100-499ms";
|
|
367
|
+
if (value < 1_000)
|
|
368
|
+
return "500-999ms";
|
|
369
|
+
return ">=1000ms";
|
|
370
|
+
}
|
|
371
|
+
function safeSlowChild(metadata) {
|
|
372
|
+
const value = metadata.slow_child;
|
|
373
|
+
if (typeof value !== "string" || value.length === 0)
|
|
374
|
+
return "other";
|
|
375
|
+
const script = basenameFromPath(value);
|
|
376
|
+
return KNOWN_HOOK_SCRIPTS.has(script) ? script : "other";
|
|
377
|
+
}
|
|
378
|
+
function isMachineWideHookSlowness(metadata) {
|
|
379
|
+
const spawnMs = metadata.spawn_ms;
|
|
380
|
+
if (typeof spawnMs === "number" && Number.isFinite(spawnMs) && spawnMs >= 500)
|
|
381
|
+
return true;
|
|
382
|
+
if (!Array.isArray(metadata.hook_sequence))
|
|
383
|
+
return false;
|
|
384
|
+
const slowScripts = new Set();
|
|
385
|
+
for (const entry of metadata.hook_sequence) {
|
|
386
|
+
if (entry.ms < 20_000 || !SAFE_HOOK_SCRIPT.test(entry.script))
|
|
387
|
+
continue;
|
|
388
|
+
slowScripts.add(entry.script);
|
|
389
|
+
}
|
|
390
|
+
return slowScripts.size >= 3;
|
|
391
|
+
}
|
|
314
392
|
function bashEnvTag(metadata) {
|
|
315
393
|
const value = metadata.bash_env_set;
|
|
316
394
|
if (value === true || value === "set")
|
|
@@ -330,7 +408,8 @@ function hookTimeoutTags(event) {
|
|
|
330
408
|
: undefined;
|
|
331
409
|
const shell = stringMetadata(metadata, "shell");
|
|
332
410
|
const nproc = metadata.nproc;
|
|
333
|
-
const loadAverage = metadata
|
|
411
|
+
const loadAverage = parsedLoadAverage(metadata);
|
|
412
|
+
const slowChildMs = metadata.slow_child_ms;
|
|
334
413
|
return {
|
|
335
414
|
hook_event: hookEvent(metadata),
|
|
336
415
|
hook_script: hookScript(metadata),
|
|
@@ -346,7 +425,14 @@ function hookTimeoutTags(event) {
|
|
|
346
425
|
shell: safeTagValue(shell),
|
|
347
426
|
bash_env: bashEnvTag(metadata),
|
|
348
427
|
cwd_kind: safeMetadataTag(metadata, "cwd_kind"),
|
|
349
|
-
|
|
428
|
+
os_name: operatingSystemName(metadata),
|
|
429
|
+
load_bucket: loadAverage === "unavailable" ? "unavailable" : loadBucket(loadAverage),
|
|
430
|
+
spawn_ms_bucket: spawnMsBucket(metadata),
|
|
431
|
+
slow_child: safeSlowChild(metadata),
|
|
432
|
+
slow_child_ms_bucket: typeof slowChildMs === "number" && Number.isFinite(slowChildMs)
|
|
433
|
+
? elapsedBucket(slowChildMs)
|
|
434
|
+
: "unknown",
|
|
435
|
+
machine_wide: isMachineWideHookSlowness(metadata) ? "true" : "false",
|
|
350
436
|
nproc: typeof nproc === "number" || typeof nproc === "string"
|
|
351
437
|
? safeTagValue(String(nproc), SAFE_NPROC)
|
|
352
438
|
: "unknown",
|
|
@@ -393,7 +479,20 @@ function hookTimeoutExceededOverrides(event) {
|
|
|
393
479
|
const eventName = hookEvent(event.metadata);
|
|
394
480
|
if (!isHookTimeoutEvent(event))
|
|
395
481
|
return undefined;
|
|
396
|
-
|
|
482
|
+
const kind = hookTimeoutKind(event);
|
|
483
|
+
if (isMachineWideHookSlowness(event.metadata)) {
|
|
484
|
+
return {
|
|
485
|
+
level: "warning",
|
|
486
|
+
fingerprint: [
|
|
487
|
+
HOOK_TIMEOUT_MACHINE_WIDE_FINGERPRINT,
|
|
488
|
+
"machine-wide",
|
|
489
|
+
operatingSystemName(event.metadata),
|
|
490
|
+
eventName,
|
|
491
|
+
kind,
|
|
492
|
+
],
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
if (kind === "warning") {
|
|
397
496
|
return {
|
|
398
497
|
level: "warning",
|
|
399
498
|
fingerprint: [HOOK_TIMEOUT_WARNING_FINGERPRINT, eventName, hookScript(event.metadata)],
|
|
@@ -536,7 +635,11 @@ function pruneExpiredRateLimitState(directory, now, protectedStatePath) {
|
|
|
536
635
|
}
|
|
537
636
|
function timeoutRateLimitLease(event, kind, options, dependencies) {
|
|
538
637
|
const sessionId = stringMetadata(event.metadata, "session_id");
|
|
539
|
-
const
|
|
638
|
+
const machineWide = isMachineWideHookSlowness(event.metadata);
|
|
639
|
+
const classification = machineWide
|
|
640
|
+
? `machine-wide\0${operatingSystemName(event.metadata)}`
|
|
641
|
+
: `per-script\0${hookScript(event.metadata)}`;
|
|
642
|
+
const key = `${sessionId}\0${hookEvent(event.metadata)}\0${kind}\0${classification}`;
|
|
540
643
|
const directory = rateLimitDirectory(options, dependencies);
|
|
541
644
|
const statePath = rateLimitStatePath(directory, key);
|
|
542
645
|
const releaseLock = acquireRateLimitLock(directory);
|