Type: Decision Status: Accepted Systems: Config, Plugins, Gateway, Daemon Author: Phil / Claude Date: 2026-08-17 Related: LLP 0031, LLP 0232, LLP 0235, LLP 0237, LLP 0238, LLP 0242, LLP 0243, LLP 0244
proxy_mode: truewith no local CA on disk is a real state, not an impossible one:hyp detach <client> --purgeproduces it by design. Attach stops treating it as "nothing to do". It names the base-URL downgrade on every attach shape, offers the one repair that fixes it (a daemon restart, which re-mints the CA and writes no config), andhyp statusreports the state as a warning instead of rendering nothing at all.
Three settled behaviours, each right on its own, compose into a dead end (found on a real macOS install, 2026-08-17):
- Attach reads the mode off the CA file, never off config (LLP 0232 #proxy-attach-preflight). The adapter cannot see config and must not promise a transport the gateway is not serving.
--purgedeletes the CA and leavesproxy_modealone. Both halves are deliberate: purge is the zero-residue exit (LLP 0238 #ca-survives-detach), and nothing on the detach path has ever written config.- The LLP 0244 migration offer returns early on
proxy_mode: true, because its own question is about writing that key.
So after hyp detach claude --purge; hyp attach claude the config asks for a
proxy, no CA exists, the daemon holds its already-loaded CA in memory and
never re-mints, attach writes a base-URL marker, and every surface reports
success. Remote Control inbound is dead and nothing says why.
The repair already exists and already works: maybeOfferProxyModeMigration
runs before the endpoint is resolved precisely so an accepted switch can
restart the daemon and let attach() find the fresh CA. This is not a missing
capability; it is an existing repair that declines to fire in the one state
that needs it.
#proxy-attach-preflight states both "a missing CA is a refusal
(markActionRefused), not a warning, and nothing is written" and, a paragraph
later, "attach uses proxy mode when a CA exists and base-URL mode otherwise".
Only the second was ever built; the only CA_MISSING refusal in the adapter
sits behind proxy mode having already been selected, so it cannot fire here.
This decision settles the contradiction in favour of the fallback, and repairs what made the fallback unsafe. Refusing outright would break the ordinary base-URL install, which is most installs, and LLP 0243/0244 already have the migration story. What was actually wrong was never the fallback; it was the silence.
proxy_mode: true covers two states and only one is settled:
| Config | CA on disk | Meaning |
|---|---|---|
proxy_mode: true |
present | genuinely in proxy mode, nothing to do |
proxy_mode: true |
absent | config and machine disagree, repair needed |
The attach-time gate returns early only for the first. The second reaches the branch below. This extends LLP 0244 #attach-offers, whose gate is keyed on the config alone: that remains correct for the migration question (which is about a config write), and the CA is what decides the second question (which is not).
Whatever else happens, an attach that is about to write base-URL mode on an
install whose config asks for a proxy says so first, on stderr, before any
question. Non-TTY, --json and hyp attach all included: those shapes may
not act, but LLP 0244 #non-interactive already owes every non-migrating
attach a line naming what was skipped, and this state is worse than the one
that rule was written for. A dry run stays the single silent shape; it changes
nothing and promises nothing.
The line names the repair. A user who reads only that line can fix the machine
by hand (hyp daemon restart, then re-attach).
An interactive, single-client, wet-run attach offers to restart the daemon,
which makes the gateway re-mint the CA (prepareInterception mints whenever
proxy_mode is on), after which the attach below takes the proxy branch it
already has. The restart-and-wait steps are LLP 0244's own; only the config
write is skipped, because the key it would set is already there.
Three properties follow from writing nothing:
- Fleet hosts are repaired too. LLP 0244 #central-managed declines locally because a local write would be dropped by the LLP 0031 merge. A restart has nothing to collide with, so the central branch does not apply here.
- It fixes the state however it was reached, not only via
--purge. - It is idempotent and cheap to decline: nothing is backed up, nothing is rewritten, and the base-URL attach proceeds as the working fallback.
A repair is not finished until the attach it enables rewrites the client. On a
daemon-managed install (no configured listen) attach short-circuits at
"already attached" whenever the client's marker names the live port, and that
port survives a restart: the marker records the mode too, and after a
re-mint the recorded one is wrong. A landed repair therefore disqualifies that
no-op, so the run that printed the success line is the run that makes it true.
An offer that changed nothing (declined, or reported with no daemon to
restart) leaves the no-op exactly as it was.
Consent is still asked, and automation is still never restarted mid-run: a daemon restart is exactly the consequential side effect LLP 0244 #non-interactive keeps out of scripts, and on macOS the attach that follows raises a keychain dialog that wants a human anyway. Where no daemon service is installed a restart cannot help, and the flow names the existing install-and-start ladder instead of claiming a repair.
Declining --purge as the fix site is deliberate. Re-minting during purge
contradicts what purge promises (a zero-residue exit), and clearing
proxy_mode during purge cannot work where it is most needed, because on a
fleet host that key is the central layer's (LLP 0031).
proxy_mode: true with no certificate rendered as nothing at all: the trust
block is built from the CA, so with no CA the whole block disappears and the
install reports healthy. Status gains a proxy_mode_ca_missing warning
diagnostic naming the state and the repair.
This is the missing half of a pair. The inverse (proxy mode off, CA present)
has always been warned about by the gateway itself as
aigw.proxy_mode_stale_ca. Unlike the trust block it is platform-independent:
the keychain and launchd halves exist only on macOS, but a config asking for a
transport the machine cannot serve is the same defect everywhere.
Warning, not error, and not degrading: a freshly-configured install whose daemon has not started yet is legitimately in this state for a few seconds, and the repair is the same either way.
A gateway entry switched off (enabled: false) is read as not asking at all,
the same reading every other plugin lookup in hyp status gives that key. A
disabled source never launches and so never mints, which would make the named
repair unable to clear its own warning however often it ran.
enableGatewayProxyModegains one outcome,remint: the config was already right, no write happened, and only the restart and the two waits ran. Itsalreadyoutcome now means "config right and CA present".- A machine can no longer report healthy while every attach it accepts lands in a mode the user did not choose.
- The adapter seam is unchanged. Attach still reads the mode off the CA and still cannot see config; the comparison between the two lives in the CLI caller, which can see both.
- The darwin half (keychain trust, the real dialog) is unprovable on Linux, so
a release touching this path wants a
docs/ACCEPTANCE.mdpass on a real Mac.