@indigoai-us/hq-cli 5.173.0 → 5.175.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,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.175.0] — 2026-09-24
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq agent kit` now shows missing external credentials at a home-relative path and reports the missing enrollment as an actionable precondition.
|
|
10
|
+
- Missing external agent credentials show an actionable error without creating a Sentry fault.
|
|
11
|
+
|
|
12
|
+
## [5.174.0] — 2026-09-24
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Outpost worktree pruning now tolerates cwd `EACCES` only for the verified
|
|
17
|
+
same-user systemd `(sd-pam)` helper. Other same-user cwd failures and all fd
|
|
18
|
+
scan failures remain fail-closed. This entry was listed under 5.173.0 by
|
|
19
|
+
mistake: 5.173.0 was tagged before the change merged, so it first ships here.
|
|
20
|
+
|
|
5
21
|
## [5.173.0] — 2026-09-24
|
|
6
22
|
|
|
7
23
|
### Fixed
|
|
@@ -46,6 +62,7 @@
|
|
|
46
62
|
|
|
47
63
|
- Switching the HQ session company (including `hq-session.sh set company_slug`) clears the Work Mesh project and task, posts `session_end` to the previous company, and holds a personal target as `needs_company`.
|
|
48
64
|
|
|
65
|
+
|
|
49
66
|
## [5.169.0] — 2026-09-24
|
|
50
67
|
|
|
51
68
|
### Fixed
|
|
@@ -37,6 +37,8 @@ import { buildWakeNotice, clearWakeConfig, currentWakeState, DEFAULT_WAKE_RETRY_
|
|
|
37
37
|
import { runSyncLoop } from "../lib/agent-kit/run/sync.js";
|
|
38
38
|
import { resolveHqBinary, resolveNodeBinary, } from "../lib/mesh/live/daemon/install.js";
|
|
39
39
|
import { installServices, servicesStatus, uninstallServices, } from "../lib/service-manager/index.js";
|
|
40
|
+
import { markExpectedUserError } from "../utils/expected-cli-error.js";
|
|
41
|
+
import { scrubHomeDirectoryPrefix } from "../utils/install-tree-torn.js";
|
|
40
42
|
export class KitError extends Error {
|
|
41
43
|
constructor(message) {
|
|
42
44
|
super(message);
|
|
@@ -47,8 +49,9 @@ export class KitError extends Error {
|
|
|
47
49
|
export function requireExternalCreds(paths) {
|
|
48
50
|
const creds = readExternalMachineCreds(paths);
|
|
49
51
|
if (!creds) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
const credentialsPath = scrubHomeDirectoryPrefix(paths.machineCredsPath, paths.home);
|
|
53
|
+
throw markExpectedUserError(new KitError(`No external agent credentials at ${credentialsPath}. Run ` +
|
|
54
|
+
`\`hq agent enroll <code>\` first (an admin creates the code in the HQ console).`));
|
|
52
55
|
}
|
|
53
56
|
return creds;
|
|
54
57
|
}
|
|
@@ -22,6 +22,8 @@ export interface ProcessPathReader {
|
|
|
22
22
|
readdirSync(path: string): string[];
|
|
23
23
|
readlinkSync(path: string): string;
|
|
24
24
|
uid(path: string): number;
|
|
25
|
+
/** Test seam for read-only process identity checks used by the cwd exception. */
|
|
26
|
+
readFileSync?(path: string): string;
|
|
25
27
|
}
|
|
26
28
|
export interface PackageStorePruneResult {
|
|
27
29
|
attempted: boolean;
|
|
@@ -521,7 +521,71 @@ const defaultProcessPathReader = {
|
|
|
521
521
|
readdirSync: (entryPath) => fs.readdirSync(entryPath),
|
|
522
522
|
readlinkSync: (entryPath) => fs.readlinkSync(entryPath),
|
|
523
523
|
uid: (entryPath) => fs.statSync(entryPath).uid,
|
|
524
|
+
readFileSync: (entryPath) => fs.readFileSync(entryPath, "utf8"),
|
|
524
525
|
};
|
|
526
|
+
function readProcFile(reader, procRoot, pid, name) {
|
|
527
|
+
if (!reader.readFileSync)
|
|
528
|
+
return null;
|
|
529
|
+
try {
|
|
530
|
+
return reader.readFileSync(path.join(procRoot, pid, name));
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
// Missing or unreadable identity metadata disables the exception.
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function procStatusIdentity(contents) {
|
|
538
|
+
if (contents === null)
|
|
539
|
+
return null;
|
|
540
|
+
const value = (name) => {
|
|
541
|
+
const prefix = `${name}:`;
|
|
542
|
+
const line = contents.split(/\r?\n/).find((entry) => entry.startsWith(prefix));
|
|
543
|
+
return line?.slice(prefix.length).trim() || null;
|
|
544
|
+
};
|
|
545
|
+
const numeric = (name) => {
|
|
546
|
+
const raw = value(name);
|
|
547
|
+
if (!raw)
|
|
548
|
+
return null;
|
|
549
|
+
const parsed = Number(raw.split(/\s+/)[0]);
|
|
550
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
|
|
551
|
+
};
|
|
552
|
+
const name = value("Name");
|
|
553
|
+
const pid = numeric("Pid");
|
|
554
|
+
const parentPid = numeric("PPid");
|
|
555
|
+
const uid = numeric("Uid");
|
|
556
|
+
if (name === null || pid === null || parentPid === null || uid === null)
|
|
557
|
+
return null;
|
|
558
|
+
return { name, pid, parentPid, uid };
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* `sd-pam` is systemd's non-dumpable PAM-session helper. Its cwd link can return
|
|
562
|
+
* EACCES to its own UID, so require the exact proc identity and user-manager
|
|
563
|
+
* ancestry before treating that one cwd as harmless.
|
|
564
|
+
*/
|
|
565
|
+
function isSystemdUserSdPamProcess(procRoot, pid, currentUid, ownerUid, reader) {
|
|
566
|
+
if (currentUid === null || ownerUid !== currentUid)
|
|
567
|
+
return false;
|
|
568
|
+
const helper = procStatusIdentity(readProcFile(reader, procRoot, pid, "status"));
|
|
569
|
+
if (!helper || helper.name !== "(sd-pam)" || helper.pid !== Number(pid) || helper.uid !== currentUid ||
|
|
570
|
+
readProcFile(reader, procRoot, pid, "comm")?.trim() !== "(sd-pam)")
|
|
571
|
+
return false;
|
|
572
|
+
const managerPid = String(helper.parentPid);
|
|
573
|
+
const manager = procStatusIdentity(readProcFile(reader, procRoot, managerPid, "status"));
|
|
574
|
+
if (!manager || manager.name !== "systemd" || manager.pid !== helper.parentPid || manager.parentPid !== 1 ||
|
|
575
|
+
manager.uid !== currentUid || readProcFile(reader, procRoot, managerPid, "comm")?.trim() !== "systemd")
|
|
576
|
+
return false;
|
|
577
|
+
const init = procStatusIdentity(readProcFile(reader, procRoot, "1", "status"));
|
|
578
|
+
if (!init || init.name !== "systemd" || init.pid !== 1 || init.parentPid !== 0 || init.uid !== 0 ||
|
|
579
|
+
readProcFile(reader, procRoot, "1", "comm")?.trim() !== "systemd")
|
|
580
|
+
return false;
|
|
581
|
+
try {
|
|
582
|
+
return reader.uid(path.join(procRoot, managerPid)) === currentUid && reader.uid(path.join(procRoot, "1")) === 0;
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
// A vanished or inaccessible parent does not prove the helper identity.
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
525
589
|
function scanProcessPaths(procRoot, childName, currentUid = currentProcessUserId(), reader = defaultProcessPathReader) {
|
|
526
590
|
const paths = [];
|
|
527
591
|
let entries;
|
|
@@ -547,7 +611,7 @@ function scanProcessPaths(procRoot, childName, currentUid = currentProcessUserId
|
|
|
547
611
|
continue;
|
|
548
612
|
}
|
|
549
613
|
const base = path.join(processDirectory, childName);
|
|
550
|
-
//
|
|
614
|
+
// Foreign UID access stays ignorable; same-UID EACCES is excepted only for a verified sd-pam cwd below.
|
|
551
615
|
let descriptors;
|
|
552
616
|
if (childName === "fd") {
|
|
553
617
|
try {
|
|
@@ -573,8 +637,10 @@ function scanProcessPaths(procRoot, childName, currentUid = currentProcessUserId
|
|
|
573
637
|
}
|
|
574
638
|
catch (err) {
|
|
575
639
|
const code = err.code;
|
|
576
|
-
|
|
577
|
-
|
|
640
|
+
const inaccessibleForeign = code === "EACCES" && currentUid !== null && ownerUid !== currentUid;
|
|
641
|
+
const knownSystemdCwd = code === "EACCES" && childName === "cwd" &&
|
|
642
|
+
isSystemdUserSdPamProcess(procRoot, pid, currentUid, ownerUid, reader);
|
|
643
|
+
if (code !== "ENOENT" && code !== "ESRCH" && !inaccessibleForeign && !knownSystemdCwd)
|
|
578
644
|
complete = false;
|
|
579
645
|
}
|
|
580
646
|
}
|