Skip to content

detach --purge then attach silently downgrades claude to base-URL mode, with no warning on any surface #819

Description

@philcunliffe

hyp detach claude --purge followed by hyp attach claude silently lands in base-URL mode, and nothing on any surface says so.

Found on a coworker's real macOS install while diagnosing a separate two-password-dialog report. Reproduces by reading on master at a23a3ea (the tagged, unpublished v1.23.0).

Repro

On a machine with proxy_mode: true in the effective config and Claude attached in proxy mode:

hyp detach claude --purge
hyp attach claude

Expected: attach re-establishes proxy mode, minting and trusting a CA (one password dialog).

Actual: attach reports success and writes a base-URL marker. No CA is minted, no keychain dialog appears, NODE_EXTRA_CA_CERTS/HTTPS_PROXY are never written, and Remote Control inbound stays broken. No warning is printed and no diagnostic appears in hyp status.

Why

Three correct-in-isolation behaviours compose into a dead end.

1. Attach decides its mode from the CA file, not from config.

hypaware-core/plugins-workspace/claude/src/index.js:200

const ca = await readLocalCaInfo({ stateRoot: defaultStateRoot(ctx.env) })
const result = await attach({
  ...,
  ...(ca ? { mode: MODE_PROXY, caCertPath: ca.certPath } : {}),
})

This is deliberate (LLP 0232 #proxy-attach-preflight): "the mode it writes is the mode the gateway is serving, not the one someone asked for." Attach never mints a CA; only the daemon does, in prepareInterception (hypaware-core/plugins-workspace/ai-gateway/src/source.js:479), which runs at gateway source launch or reload.

2. --purge deletes the CA but leaves proxy_mode on and does not restart the daemon.

purgeProxyTrustResidue (src/core/commands/clients.js:1586) removes the CA files, the keychain trust, and the launchd env. Nothing in the detach path writes proxy_mode, so the config still claims proxy mode while no CA exists. The running daemon holds its already-loaded CA in memory and has no reason to relaunch the source, so nothing re-mints.

3. The migration prompt that would repair this returns early.

src/core/commands/clients.js:842

if (effectiveGateway?.config?.proxy_mode === true) return

The offer to switch an install to proxy mode is skipped precisely because the config says proxy mode is already on. The config is right about intent and wrong about reality, and this is the one place that could notice.

Why it is silent

  • Attach's platform-warning branch is if (ca && darwin) {...} else if (ca) {...}. There is no else for the missing-CA case, so a downgrade produces no warning (.../claude/src/index.js:225).
  • collectProxyTrust bails on if (!ca) return null (src/core/daemon/status.js:1210), so the entire proxy trust: block disappears from hyp status rather than reporting a problem.

The result is an install that reports healthy while capture has quietly changed shape and Remote Control inbound is dead.

Recovery (works today, for the runbook)

hyp daemon restart
ls ~/.hyp/hypaware/tls/ca-cert.pem   # must exist before attaching
hyp attach claude

The restart re-mints the CA because prepareInterception sees proxy_mode: true. Attach then takes the proxy branch. The base-URL to proxy switch itself is sound: releaseUnmanagedKeys (.../claude/src/settings.js:479, LLP 0232 #mode-migration) restores or removes the keys the old mode owned, so no manual settings editing is needed and no detach is required first.

Re-running --purge before re-attaching deletes the freshly minted CA and returns to the same state.

Options

Not proposing a fix, since which surface owns the truth is a design call:

  1. Make --purge leave a consistent world. Either restart the gateway source as part of the purge so the CA is re-minted, or clear proxy_mode so config and disk agree.
  2. Let attach report the mismatch. Attach cannot see config by design, but its CLI caller can: at clients.js:842, distinguish "proxy_mode true and a CA exists" from "proxy_mode true and no CA", and on the latter say so and name the repair instead of returning.
  3. Make hyp status name the state. proxy_mode: true with no CA on disk is currently invisible; today collectProxyTrust returns null for it. The inverse case (proxy mode off, CA present) is already warned about as aigw.proxy_mode_stale_ca in source.js:441, so this is the missing half of an existing pair.

(3) is the smallest honest change and is independently useful; (1) is what stops users reaching the state at all.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneutral:fixDelegate this issue to neutral for an autonomous fix attempt (reproduce -> fix -> PR)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions