@indigoai-us/hq-cli 5.109.4 → 5.109.5

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,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.109.5] — 2026-09-10
6
+
5
7
  ## [5.109.4] — 2026-09-09
6
8
 
7
9
  ### Fixed
@@ -53,11 +53,21 @@ const TRANSPORT_CODE_REASONS = {
53
53
  * Recognized undici error names, for builds where the `code` property is
54
54
  * absent but the typed error still identifies itself (the shape Sentry
55
55
  * recorded for HQ-CLI-G was `ConnectTimeoutError`).
56
+ *
57
+ * `TimeoutError` is the DOMException `AbortSignal.timeout(...)` rejects a
58
+ * bounded `fetch()` with when the peer accepts the connection but never answers
59
+ * — Sentry HQ-CLI-1P (issue 7716090679). It is the one transport failure here
60
+ * that identifies itself ONLY by name: its legacy DOMException `code` is the
61
+ * NUMBER 23, so the string-typed `readStringProperty(node, "code")` above skips
62
+ * it, and it carries no `cause` to walk, leaving just the name. `AbortError` is
63
+ * deliberately NOT listed — a caller-initiated abort is not a transport fault
64
+ * and must stay reportable.
56
65
  */
57
66
  const TRANSPORT_NAME_REASONS = {
58
67
  ConnectTimeoutError: "the connection timed out",
59
68
  HeadersTimeoutError: "the server did not respond in time",
60
69
  SocketError: "the connection closed unexpectedly",
70
+ TimeoutError: "the server did not respond in time",
61
71
  };
62
72
  /**
63
73
  * Depth cap for `cause` traversal. A self-referential or mutually-referential
@@ -19,6 +19,24 @@ import { planGateErrorFromResponse } from './plan-gate-error.js';
19
19
  * whole-process watchdog.
20
20
  */
21
21
  const IDENTITY_LOOKUP_TIMEOUT_MS = 15_000;
22
+ /**
23
+ * The identity-lookup abort bound, in milliseconds. Defaults to the shipped
24
+ * IDENTITY_LOOKUP_TIMEOUT_MS (15s). An optional HQ_IDENTITY_LOOKUP_TIMEOUT_MS
25
+ * override (clamped to a sane 100..120_000 ms window; absent or non-numeric
26
+ * input keeps the 15s default) exists purely so the artifact E2E can force the
27
+ * abort in well under a second instead of stalling a spawned CLI for the full
28
+ * 15s per case. It does NOT change shipped behaviour: with the var unset every
29
+ * call uses 15s exactly as before.
30
+ */
31
+ function identityLookupTimeoutMs() {
32
+ const raw = process.env.HQ_IDENTITY_LOOKUP_TIMEOUT_MS?.trim();
33
+ if (!raw)
34
+ return IDENTITY_LOOKUP_TIMEOUT_MS;
35
+ const parsed = Number(raw);
36
+ if (!Number.isFinite(parsed))
37
+ return IDENTITY_LOOKUP_TIMEOUT_MS;
38
+ return Math.min(120_000, Math.max(100, Math.trunc(parsed)));
39
+ }
22
40
  /**
23
41
  * Best-effort peek of a 2xx JSON body for plan-limit status (US-016).
24
42
  *
@@ -279,7 +297,7 @@ async function resolveCompanyByUid(token, uid) {
279
297
  const res = await vaultApiFetch({
280
298
  token,
281
299
  path: `/entity/${encodeURIComponent(uid)}`,
282
- signal: AbortSignal.timeout(IDENTITY_LOOKUP_TIMEOUT_MS),
300
+ signal: AbortSignal.timeout(identityLookupTimeoutMs()),
283
301
  });
284
302
  if (!res.ok) {
285
303
  raiseIfUnauthorized(res);
@@ -312,7 +330,7 @@ async function resolveSlugInCallerNamespace(token, slug) {
312
330
  token,
313
331
  path: '/entity/check-slug/me',
314
332
  query: { type: 'company', slug },
315
- signal: AbortSignal.timeout(IDENTITY_LOOKUP_TIMEOUT_MS),
333
+ signal: AbortSignal.timeout(identityLookupTimeoutMs()),
316
334
  });
317
335
  if (!res.ok) {
318
336
  raiseIfUnauthorized(res);
@@ -336,7 +354,28 @@ async function resolveCompanyUid(token, ref) {
336
354
  // PRIMARY PATH — caller-scoped slug resolution. Resolves the slug to the
337
355
  // caller's OWN company (unique within their namespace by the invariant
338
356
  // above), making a stranger's same-slug company invisible.
339
- const mine = await resolveSlugInCallerNamespace(token, ref);
357
+ //
358
+ // A non-2xx already degrades to the global fallback below (via `return null`).
359
+ // A TRANSPORT failure must degrade the SAME way, not abort the command:
360
+ // `resolveSlugInCallerNamespace`'s fetch is bounded by AbortSignal.timeout, so
361
+ // a vault gateway that accepts the connection but never answers rejects with a
362
+ // DOMException named `TimeoutError`. Before this catch, that rejection threw
363
+ // straight out of `resolveCompanyUid`, skipped the global by-slug lookup, and
364
+ // — being unclassified by main.ts — filed a Sentry crash report instead of an
365
+ // actionable connectivity message (Sentry HQ-CLI-1P / issue 7716090679).
366
+ // Degrade ONLY on a recognized transport failure; re-throw everything else
367
+ // unchanged, so a genuine hq-cli defect stays fatal and the 401 AuthError that
368
+ // `raiseIfUnauthorized` throws still short-circuits before any fallback.
369
+ let mine;
370
+ try {
371
+ mine = await resolveSlugInCallerNamespace(token, ref);
372
+ }
373
+ catch (err) {
374
+ if (networkTransportErrorCode(err) === null) {
375
+ throw err;
376
+ }
377
+ mine = null;
378
+ }
340
379
  if (mine) {
341
380
  return mine;
342
381
  }
@@ -347,7 +386,7 @@ async function resolveCompanyUid(token, ref) {
347
386
  const res = await vaultApiFetch({
348
387
  token,
349
388
  path: `/entity/by-slug/company/${encodeURIComponent(ref)}`,
350
- signal: AbortSignal.timeout(IDENTITY_LOOKUP_TIMEOUT_MS),
389
+ signal: AbortSignal.timeout(identityLookupTimeoutMs()),
351
390
  });
352
391
  if (!res.ok) {
353
392
  raiseIfUnauthorized(res);
@@ -374,7 +413,7 @@ async function resolveCompanyFromMemberships(token) {
374
413
  const res = await vaultApiFetch({
375
414
  token,
376
415
  path: '/membership/me',
377
- signal: AbortSignal.timeout(IDENTITY_LOOKUP_TIMEOUT_MS),
416
+ signal: AbortSignal.timeout(identityLookupTimeoutMs()),
378
417
  });
379
418
  if (!res.ok) {
380
419
  throw new Error("Failed to fetch memberships — run `hq login` and try again");
@@ -421,7 +460,7 @@ export async function resolveCallerPersonUid(token, baseUrl) {
421
460
  token,
422
461
  path: '/entity/by-type/person',
423
462
  baseUrl,
424
- signal: AbortSignal.timeout(IDENTITY_LOOKUP_TIMEOUT_MS),
463
+ signal: AbortSignal.timeout(identityLookupTimeoutMs()),
425
464
  });
426
465
  if (!res.ok) {
427
466
  throw new Error("Failed to fetch person entity — run `hq login` and try again");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.109.4",
3
+ "version": "5.109.5",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {