From f78e4445c76948ca85c9f84ebb9a2850cce20969 Mon Sep 17 00:00:00 2001 From: neutral Date: Tue, 18 Aug 2026 06:40:06 +0000 Subject: [PATCH] The stale-CA warning names a remedy that cannot work, and AGENTS.md omits openclaw_capture (#831) Two independently reachable findings from the #815 triage, both pre-existing on master and outside that PR's diff. `aigw.proxy_mode_stale_ca` fires when `proxy_mode` is off but an interception CA is still on disk, and told the operator to "run `hyp attach claude` to move it back to base-URL mode". Attach derives its mode from that same CA (LLP 0232 #proxy-attach-preflight), so in exactly this state a re-attach picks proxy mode again: the named remedy is a no-op. The warning now names the two that land, removing the CA with `hyp detach claude --purge` and re-attaching, or turning `proxy_mode` back on. The existing blind-tunnel test pins the reason string so the wrong remedy cannot come back. AGENTS.md's "Written acceptance procedures" list has named only `codex_desktop_capture` since #570 added `openclaw_capture` to docs/ACCEPTANCE.md, so the second written procedure was invisible to anyone reading the repo guidance rather than the doc. Listed it. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 7 +++++++ .../plugins-workspace/ai-gateway/src/source.js | 14 ++++++++++---- test/plugins/ai-gateway-proxy-mode.test.js | 18 +++++++++++++++--- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d87b9819..bc2d5c6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -96,6 +96,13 @@ Written acceptance procedures: Proves Desktop traffic reaches `ai_gateway_messages` by both the live gateway route and the `~/.codex/sessions` backfill route, and is attributable via `entrypoint`. See `docs/ACCEPTANCE.md`. +- `openclaw_capture`: opt-in/manual, needs OpenClaw 2026.4.24 or newer with + credentials for both `anthropic` and `openai`. Proves an OpenClaw + conversation reaches `ai_gateway_messages` by both lanes the adapter + offers, live gateway capture after `hyp attach openclaw` and the periodic + transcript sweep, that a turn both lanes observe settles to exactly one + row rather than two, and that live capture is reversible via + `hyp detach`. See `docs/ACCEPTANCE.md`. Good acceptance smoke candidates (no written procedure yet): diff --git a/hypaware-core/plugins-workspace/ai-gateway/src/source.js b/hypaware-core/plugins-workspace/ai-gateway/src/source.js index 9449b687..6e5fc61f 100644 --- a/hypaware-core/plugins-workspace/ai-gateway/src/source.js +++ b/hypaware-core/plugins-workspace/ai-gateway/src/source.js @@ -432,9 +432,13 @@ async function prepareInterception(ctx, config, upstreams, liveState) { if (!config.proxyMode) { // Proxy mode is off, but a CA on disk means a client was attached in proxy // mode at some point and may still have `HTTPS_PROXY` pointing here. Serve - // blind tunnels so its egress keeps working, and say so loudly: the repair - // is a re-attach (or a detach), and nothing else on the machine will - // volunteer that. + // blind tunnels so its egress keeps working, and say so loudly: nothing + // else on the machine will volunteer that this install is in the degraded + // state. A plain re-attach is NOT the remedy: attach reads the mode off + // this very CA, so it picks proxy mode again for as long as the file is + // here. The remedies that land are removing the CA (`hyp detach claude + // --purge`, then re-attach) or turning `proxy_mode` back on. + // @ref LLP 0232#proxy-attach-preflight [constrained-by]: attach derives the mode from this CA, so it cannot downgrade while the CA is on disk const stale = await readLocalCaInfo({ stateRoot: defaultStateRoot(ctx.env) }) if (stale) { liveState.interceptionError = 'proxy_mode is off but a local CA is installed' @@ -442,7 +446,9 @@ async function prepareInterception(ctx, config, upstreams, liveState) { [Attr.PLUGIN]: PLUGIN_NAME, ca_cert_path: stale.certPath, reason: 'serving blind tunnels so an already-attached client keeps working; ' + - 'run `hyp attach claude` to move it back to base-URL mode, or `hyp detach claude`', + 're-attach reads the mode off this CA, so run `hyp detach claude --purge` ' + + 'and re-attach to move the client back to base-URL mode, or turn ' + + 'proxy_mode back on', }) return { tunnelOnly: true } } diff --git a/test/plugins/ai-gateway-proxy-mode.test.js b/test/plugins/ai-gateway-proxy-mode.test.js index 32839fa8..9bf4a1c3 100644 --- a/test/plugins/ai-gateway-proxy-mode.test.js +++ b/test/plugins/ai-gateway-proxy-mode.test.js @@ -555,10 +555,13 @@ test('proxy mode turned off with a CA still installed serves blind tunnels', asy const echoPort = echoAddress && typeof echoAddress === 'object' ? echoAddress.port : 0 t.after(() => new Promise((resolve) => echo.close(() => resolve(undefined)))) - /** @type {{ level: string, event: string }[]} */ + /** @type {{ level: string, event: string, attrs: Record }[]} */ const logged = [] /** @param {string} level */ - const record = (level) => (/** @type {string} */ event) => logged.push({ level, event }) + const record = (level) => ( + /** @type {string} */ event, + /** @type {Record | undefined} */ attrs + ) => logged.push({ level, event, attrs: attrs ?? {} }) const ctx = /** @type {any} */ ({ // proxy_mode deliberately absent, as if the operator turned it back off. config: { @@ -576,7 +579,16 @@ test('proxy mode turned off with a CA still installed serves blind tunnels', asy const details = /** @type {any} */ ((await source.status()).details) assert.equal(details.proxy_mode, false) assert.match(details.proxy_mode_error, /a local CA is installed/) - assert.equal(logged.some((l) => l.event === 'aigw.proxy_mode_stale_ca'), true) + const staleWarn = logged.find((l) => l.event === 'aigw.proxy_mode_stale_ca') + assert.ok(staleWarn, 'the stale-CA warning is emitted') + + // The warning has to name a remedy that actually works. Attach derives its + // mode from this same CA, so telling the operator to re-attach leaves them + // exactly where they started: proxy mode, every time, until the CA is gone. + // @ref LLP 0232#proxy-attach-preflight [tests]: the stale-CA remedy cannot be a plain re-attach + const reason = String(staleWarn.attrs.reason ?? '') + assert.match(reason, /hyp detach claude --purge/) + assert.doesNotMatch(reason, /run `hyp attach claude` to move it back/) // The tunnel is still served, so an already-attached client keeps its egress // instead of losing all HTTPS. (The byte-level round trip is covered by