Uh oh!
There was an error while loading. Please reload this page.
Attach and status residuals: bound the macOS status probes, drop the contradictory enablement line, reach both attach tails - #900
Conversation
The `reported` bit added in be2b4e3 suppressed the caller's pre-write guided error for every `enableClientAdapter` failure, including `failedStep === 'write'`. That shape wrote nothing, so the guided error does not contradict it - and it is the only line naming the config path and the manual remedy. On an unwritable config the user was left with "the config write failed (...); nothing changed" and no next step. Scope `reported` to the failures below the write (`restart` / `wait`, and the incomplete in-process activation), which are the ones whose own report describes state that now exists on disk. Pinned by a new case in test/core/attach-enable-resume.test.js that fails on be2b4e3. Also corrects the daemon-managed re-arm comment: with no live endpoint to compare against, "already correct is the same fact as just written" is not true, so the comment now rests on what LLP 0186 actually scopes the re-arm to (the explicit manual re-run), not on a write having happened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
philcunliffe
commented
Aug 19, 2026
Neutral review round: |
…probe in parallel (#900) Three review findings on the daemon-managed attach exit and the bounded macOS status probes, plus the LLP the first of them was missing. - The daemon-managed exit materialized assets *before* re-arming. The two tails are independent, so the order only shows on failure, and there it matters one way: `materializeAttachAssets` swallows a per-copy failure but not its plan read, prune pass, or asset digest, and a throw there lands in the loop's outer catch. With the re-arm second, that failure left the `refused` marker short-circuiting the reconciler forever, after exactly the explicit re-run LLP 0186 makes its only trigger. Both exits now re-arm first, the order the freshly-wired exit already used. - `reportAttachEnablement` was skipped whole when the enable prompt had already reported. It also emits the `client.attach.adapter_inactive` warn and the `--json` payload, neither of which any prompt writes, so a failed attach could leave no machine record at all. It takes a `quiet` bit now that drops only the human line, matching the capability gate above it, which has always suppressed only its `stderr.write`. - `collectProxyTrust` ran its two bounded probes in sequence, so the wedged host this bound exists for waited two deadlines to render. They read unrelated state and neither reads the other's answer, so they start together under `Promise.allSettled`, which keeps the per-probe independence the catches gave. New LLP 0266 settles what the first item assumed and LLP 0186 does not state: the re-arm fires at every success exit of an explicit `hyp attach`, including the one that calls no adapter `attach()` hook, and runs ahead of the asset tail. LLP 0186 keeps its text and gains the forward-ref. `test/core/attach-daemon-managed-tails.test.js` gains the ordering case: an asset tail that throws after the re-arm still leaves the marker cleared. It fails with the tails in the old order. npm test: 4492 pass, 0 fail, 1 skipped. npm run typecheck: clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
philcunliffe
commented
Aug 19, 2026
Neutral review round: |
philcunliffe
commented
Aug 19, 2026
Note on the new The reviewer could not have seen this from inside the branch, which is exactly the failure issue #907 describes and PR #916 gates. Full claimant table re-derived from every ref: #907. This document will need renumbering before or at merge, depending on the order you land these. |
philcunliffe
commented
Aug 19, 2026
Triage at the review-round cap, head Verified against the code at head: every finding from both review rounds is fixed there.
Two residuals, both non-blocking, deferred to #919:
No blocker: nothing residual can cause wrong behavior, data loss, a security hole, a crash, or a perf regression. The PR can merge safely once a human lands it. |
philcunliffe
commented
Aug 19, 2026
Triage at the review-round cap, head The head moved past the last triaged commit (
Two residuals, both non-blocking, remain deferred to #919:
No blocker: nothing residual can cause wrong behavior, data loss, a security hole, a crash, or a perf regression. The PR can merge safely once a human lands it. |
…ictory enablement line, reach both attach tails (#887) Three residuals on the attach and status paths, each reproduced by a test that failed before the change. 1. `hyp status` could hang forever. `collectProxyTrust` shells out twice on darwin with a CA on disk (`security verify-cert`, `launchctl getenv`), and `runServiceCommand` only ever settled on the child's `close`: a probe that never returned was not a caught error, it was a command that printed nothing and never exited. macOS trust evaluation can reach the network for revocation, so an offline or captive-portal host is the trigger. `runServiceCommand` gains an opt-in `timeoutMs` that kills the child, detaches its handles so the caller can still exit, and rejects; the two read-only probes pass it, so the trust line reads unknown instead of holding up the report. Mutating commands (`launchctl bootstrap`, `security add-trusted-cert` behind its password dialog) pass nothing and keep the unbounded wait they have always had. 2. A partial enable no longer contradicts itself. When the accept path's config write landed but the restart, wait, or in-process activation step did not, the prompt printed an accurate report ("the config change already persists ... re-running resumes from the new state") and the caller then printed `enablement.message`, computed before the write: "not enabled ... add @hypaware/claude to <config> and run 'hyp daemon restart'". LLP 0174 #prompt says each step reports its own failure, so the prompt now says it reported one and the caller stays quiet. 3. The daemon-managed "already attached at the live port" branch reaches both tails below `client.attach()`. It used to `continue` right after materializing assets, so on the install shape an explicit `hyp attach` is most often run on, LLP 0186's re-arm (the only re-arm a `refused` marker gets) never happened, and LLP 0174 step 4's backfill offer, which the accept path exists to reach, was never made. The re-arm moves into a shared helper so neither success exit can silently lose it again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `reported` bit added in be2b4e3 suppressed the caller's pre-write guided error for every `enableClientAdapter` failure, including `failedStep === 'write'`. That shape wrote nothing, so the guided error does not contradict it - and it is the only line naming the config path and the manual remedy. On an unwritable config the user was left with "the config write failed (...); nothing changed" and no next step. Scope `reported` to the failures below the write (`restart` / `wait`, and the incomplete in-process activation), which are the ones whose own report describes state that now exists on disk. Pinned by a new case in test/core/attach-enable-resume.test.js that fails on be2b4e3. Also corrects the daemon-managed re-arm comment: with no live endpoint to compare against, "already correct is the same fact as just written" is not true, so the comment now rests on what LLP 0186 actually scopes the re-arm to (the explicit manual re-run), not on a write having happened. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…probe in parallel (#900) Three review findings on the daemon-managed attach exit and the bounded macOS status probes, plus the LLP the first of them was missing. - The daemon-managed exit materialized assets *before* re-arming. The two tails are independent, so the order only shows on failure, and there it matters one way: `materializeAttachAssets` swallows a per-copy failure but not its plan read, prune pass, or asset digest, and a throw there lands in the loop's outer catch. With the re-arm second, that failure left the `refused` marker short-circuiting the reconciler forever, after exactly the explicit re-run LLP 0186 makes its only trigger. Both exits now re-arm first, the order the freshly-wired exit already used. - `reportAttachEnablement` was skipped whole when the enable prompt had already reported. It also emits the `client.attach.adapter_inactive` warn and the `--json` payload, neither of which any prompt writes, so a failed attach could leave no machine record at all. It takes a `quiet` bit now that drops only the human line, matching the capability gate above it, which has always suppressed only its `stderr.write`. - `collectProxyTrust` ran its two bounded probes in sequence, so the wedged host this bound exists for waited two deadlines to render. They read unrelated state and neither reads the other's answer, so they start together under `Promise.allSettled`, which keeps the per-probe independence the catches gave. New LLP 0266 settles what the first item assumed and LLP 0186 does not state: the re-arm fires at every success exit of an explicit `hyp attach`, including the one that calls no adapter `attach()` hook, and runs ahead of the asset tail. LLP 0186 keeps its text and gains the forward-ref. `test/core/attach-daemon-managed-tails.test.js` gains the ordering case: an asset tail that throws after the re-arm still leaves the marker cleared. It fails with the tails in the old order. npm test: 4492 pass, 0 fail, 1 skipped. npm run typecheck: clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1df26b5 to
982c524CompareUh oh!
There was an error while loading. Please reload this page.
All three residuals in #887 reproduced, and all three are fixed. Each has a
test that failed on
masterbefore the change and passes after.1.
hyp statuscould hang indefinitely (finding 1)collectProxyTrustshells out twice on darwin with a CA on disk, andrunServiceCommandonly ever settled on the child'sclose. Its try/catchturns a thrown probe into
null, but a probe that never returns is not athrow:
hyp statusprinted nothing and never exited.runServiceCommandgains an opt-intimeoutMs. On the deadline it killsthe child, destroys its stdio and unrefs it (so the caller can exit even if
a grandchild still holds the inherited pipe), and rejects with a new
ServiceCommandTimeoutError. The two read-only probes (isCaTrusted,isLaunchdEnvSet) passSERVICE_PROBE_TIMEOUT_MS, so the rejection lands inthe existing catch and the trust line reads unknown, which is the honest
answer, rather than holding up the whole report.
The bound is deliberately opt-in:
launchctl bootstrapandsecurity add-trusted-certsitting behind its password dialog maylegitimately block for minutes and must not be cut off mid-write, so they
pass nothing and keep the unbounded wait they have always had. Only a
bounded caller is ever killed, so the
SIGKILLcan never interrupt ahalf-applied mutation.
Reproduced in
test/core/service-command-timeout.test.js. Both cases drive areal
shstub that never returns, from a child process: LLP 0181's guardrefuses every spawn inside the test runner, and a fake child would prove
nothing about killing a real one. The second case runs the real
collectHypAwareStatuswith real probes against asecurityonPATHthatnever answers, and asserts the report still renders. Before the fix both runs
had to be killed by
spawnSync's own timeout ('SIGTERM' !== null).2. Contradictory enablement message after a partial attach (finding 2)
When
maybeInteractiveEnableAttachgot pastenableClientAdapterbut stillreturned
{ activated: false }, it had already reported the failed step andthe state it left on disk. The caller then unconditionally printed
enablement.message, computed before the write, which denies that write andinstructs a config edit already made.
maybeInteractiveEnableAttachnow returns areportedbit, set at the twoexits that have already written their own message (
reportEnableFailure, andthe
activation_incompletepath), and both call sites skip the pre-writeguided error when it is set. Every early return and the decline are unchanged:
none of them wrote anything, so the caller's message is still the right one
there.
--jsonand non-TTY callers never reach the prompt at all, so theirpayloads are untouched. This is LLP 0174 #prompt's "each step reports its own
failure", read as one report per failure.
Covered in
test/core/attach-enable-resume.test.js: the existingwrite-succeeded/restart-failed case now also pins that exactly one
error:line is printed, and a new case does the same for the activation-incomplete
exit.
3. Skipped re-arm and backfill offer (finding 3)
The daemon-managed "already attached at the live port" branch
continuedright after
materializeAttachAssets, so it reached neither tail belowclient.attach(). That made it the one attach path where LLP 0186's re-arm,the only re-arm a
refusedmarker ever gets and whose sole trigger is thisexplicit re-run, never happened, and where LLP 0174 step 4's backfill offer,
the whole point of the accept path, was never made.
Both tails are now reached from that branch too. The re-arm moved into a
shared
rearmRefusedAttachMarkerhelper (unchanged logic, samerefused-onlyscope and
--dry-runskip) so neither of attach's two success exits cansilently lose it again. Reaching it with the settings already current rather
than freshly written changes nothing either tail depends on: every attach
adapter is idempotent over its own output by construction, so "already
correct" is the same fact as "just written", and the offer's precondition is
that this invocation enabled the adapter.
New file
test/core/attach-daemon-managed-tails.test.js, four cases: there-arm fires on that exit; a
donemarker and itsinstalled_assetsstillsurvive it (LLP 0138 #marker-undo); the post-enable backfill offer is reached
after the enable prompt lands on it; and an adapter that was already enabled
still reaches no offer. Cases 1 and 3 failed before the change.
Checks
npm test: 4490 pass, 0 fail, 1 skippednpm run typecheck: cleannpm run smoke -- status_diagnostics,npm run smoke -- client_attach_idempotent: okNothing in #887 was left unfixed.
Fixes#887