Repository navigation
fix: keep the route token off the Claude CLI command line - #6
Conversation
The pinned claude-agent-acp merges a providers/set route's headers into the env of the programmatic settings, which the Claude Agent SDK passes to the CLI as a --settings argument, so the temporary token was readable by anyone listing processes. The token now reaches Claude Code only through the adapter's spawn environment (ANTHROPIC_CUSTOM_HEADERS). The routing without it, with apiKeyHelper cleared and every other upstream and credential source reset, goes into the flag settings of every session setup, which still outrank a repository's or user's settings. providers/set is no longer sent, so a running adapter is already on its route.
Runs the claude-agent-acp and Claude Code CLI versions upstream.lock.json pins on a platform route and fails if any process they start shows the token in its arguments. It needs the real pair and Node, so it is its own task (deno task test:argv) and CI job, which installs the pair from npm at the pinned versions.
wanglongan587
left a comment
There was a problem hiding this comment.
审查版本:6f8fd2031f88ac1ef8c7d8a257316b8318842d7d(base 434872a,3 个 commit,9 个文件)。结论:Approve(无阻塞)。
我核对过的
- 本地在 detached worktree(审查 head)上跑
deno task check、deno task lint、deno task test:全部通过,45 passed | 0 failed。把 9 个改动文件 LF 归一后在临时目录跑deno fmt --check通过(Windows 检出 CRLF 会误报);git diff --check通过。 - PR CI:
check、argv均 SUCCESS(run39711150478,head 就是6f8fd20)。argvjob 日志明确显示真实跑了 pinned pair:the pinned adapter and CLI never put the token on a command line ... ok (646ms),1 passed | 0 failed,不是被 skip;job 按upstream.lock.json从 npm 装了 adapter 0.94.0 / SDK 0.3.303。 - 按 pinned
@agentclientprotocol/claude-agent-acp@0.94.0的 dist 源码核对:- 没有
providers/set/authenticate时,resolveProviderConfig()为null;session 路径providerEnv = createEnvForProvider(null) = {},settings就是本插件经_meta.claudeCode.options.settings给的configuredSettingsObject,env为process.env + caller env + {}(acp-agent.js8426-8453)。 createEnvForProvider的resetRouting与本 PR 的routingEnvironment一致;占位 bearer 的实际值就是acp-proxy(gateway-models.js)。OPTION_REBUILDS_SESSION.settings: true,computeSessionFingerprint覆盖 settings,session/load|resume会按 settings 重建 query。defaultProviderConfig()只被unstable_listProviders使用,而ModelGuard拒绝providers/*。
- 没有
- 跨仓库核对 desktop
crates/plugin-lifecycle/src/childprocess.rs(desktop upstream/maindf5da60):request env 经ProcessSpec::env添加、host env 之后应用(ProcessSpec注释是 "Adds or overrides"),spawn 路径不记录 env;plugin-sdk 也写明 env 是 "Added to the environment the child inherits from the host; it is not a replacement." PR 描述里 host 不记录 env 的声明在 desktop main 上成立。 - 路由与模型收紧没有被删:
ModelGuard仍拒绝authenticate/logout/auth/*/providers/*,session/set_model/model config 只允许冻结模型,agent/list_models在 route 下直接回答冻结模型;tests/model_route_test.ts覆盖 token 只出现在 spawn env、每个 session setup 都带 settings(含load/resume/fork)、Effect restart 用同一 route。 - 文件列表全是交付物;3 个 commit 分别是 fix(含行为测试)、argv/CI、docs,拆分合理。ADR 依据 specs
0434c821的plugin-owned-model-adapters仍为approved;本 PR 只实现一部分、真实 Node 与 OpenCode 检查留给 #452、implemented由 #467 承接,这个判断正确。
非阻塞(建议顺手改,不影响合并)
- 两处注释仍在描述旧的 route 接受语义:
src/handlers/lifecycle.ts:18-21:"It is confirmed only after the adapter accepted the route built from it, which is what the host waits for before it lets ACP begin"。现在 route 由 spawn env 应用,startClaude在 spawn 后直接返回 applied,没有"adapter 接受"的握手;建议改成"spawn 成功即 route 已应用"。src/handlers/effects.ts:229:"a route the adapter refused or never answered"。这个失败模式已随providers/set一起删除,现在 restart 的失败只有 spawn 失败;建议改掉,避免以后有人按旧语义加回请求/超时。
这两处不在本 PR 改动文件里,但 PR 改变了它们描述的契约。
agent/start不再做 adapter 活体探测:现在 spawn 成功即返回 applied。gateway 不可用已在 PR 和运行 issue 写明;但 adapter 进程 spawn 后立即退出(例如 bundled CLI 缺失/不可执行,adapter-entry.mjs抛错)也会先返回成功,到 ACPinitialize才暴露。#466 里"bundled CLI 缺失未归为AGENT_UNUSABLE"一项与这个变化相关,建议在 PR 描述或 #466 明确承接/补测,避免以后把"start 成功"当成"adapter 可用"。- argv 测试只覆盖
session/new:load|resume|fork的 settings 注入目前只有 ScriptedAdapter 单测。pinned adapter 的指纹和 options 路径表明 settings 会生效,风险低,但真实 pair 没有这些路径的证据,可在后续验收或测试 harness 支持时补一条。 - PR 描述可以链到 #467:现在写 "a separate specs run",而 #481 正文已写明由 #467 承接;写上编号更便于 owner 核对 ADR 落地。
- 小项:
test:argv用--allow-all,其余 task 都是最小权限;argvjob 的 npm 安装没有校验upstream.lock.json的 integrity/sha256(packagetask 有),作为回归门禁可以接受,但可考虑复用同一份校验,避免测试和实际打包的版本来源不一致。
未验证
- 真实 Node 上以另一身份读进程参数(#452 验收第 1 项):本次只证明同一身份下 CLI argv 不含 token、environ 含 token。该修复把 token 从 argv 移到 env 后,Linux 上同 UID 仍可读
/proc/<pid>/environ,所以真实 Node 验收必须在 Separate placement/不同身份下做;Shared placement 的 D4 问题仍由 #466 第 1 项承接。 - OpenCode adapter 的同类检查(#452 验收第 3 项)。
- 真实 prompt/网关请求、仓库
.claude/settings.json覆盖:PR 描述里是手工 fake 网关验证,没有在 CI 自动化。 - 真实 bundled 编译产物(Bun 单文件)没有被
argv测试直接跑到;测试用的是 npmdist/index.js+ 模拟adapter-entry.mjs的CLAUDE_CODE_EXECUTABLE。 - Windows/macOS 的 argv 可见性没有测试(测试按设计只在 Linux 跑
/proc)。
交付物 / ADR
9 个文件都是交付物;Refs ora-space/circus#481 正确。ADR 保持 approved 符合"只实现一部分"的要求;真实 Node 验收和 OpenCode 检查仍由 #452 承接,implemented 由 #467 的 specs 运行承接。parent #452 的 owner 结论(发布门禁,不是合并门禁)仍然成立:本 PR 合并不会打开发布开关。
结论:可以合并;上面的非阻塞项建议在合并前或后续 commit 处理,若改 head 请重新打 status:in-review。
agent/start no longer waits for the adapter to accept a route, and a restart can no longer fail on one; the comments said both still could.
wanglongan587
left a comment
There was a problem hiding this comment.
复审:Approve,head ad1b8ac0e80279c7ca686d93556282b1cac76c06
前次结论针对 head 6f8fd20(3 个功能/测试 commit)。本次增量只有 1 个 commit ad1b8ac,内容是两处注释,git diff 6f8fd20..ad1b8ac 为 2 文件、5+/4-,其余文件与 commit 未动。
增量核对
src/handlers/lifecycle.ts:18-22:改为"adapter 带着由冻结上下文构建的 route 环境变量 spawn 后即确认",与startClaude的实际顺序(modelRoute→useRoute→await client.start(cwd)→ 返回route.applied)一致;ready现在就是running,不再有providers/set的接受阶段。src/handlers/effects.ts:229:restart 失败原因改为"host 无法 spawn adapter",与ClaudeClient.start唯一抛错点(#spawn失败后 rethrow)一致;finally里的#release()语义未变。
src/ 与 AGENTS.md 里已无"adapter 接受/拒绝 route"的旧语义;AGENTS.md 与 model-guard.ts 中出现的 providers/set 都是"不使用它"的正当说明。
验证(head ad1b8ac)
- 本地独立 worktree:
deno task check、deno task lint、deno task test全过,45 passed | 0 failed;两个改动文件按 LF 归一后deno fmt --check通过(Windows 检出是 CRLF,本地core.autocrlf=true造成,不是 PR 内容),git diff --check干净。 - PR CI run 39714036149,head
ad1b8ac:check与argv均 SUCCESS。argv日志确认真实执行了 pinned pair(the pinned adapter and CLI never put the token on a command line ... ok (703ms),1 passed | 0 failed),不是 skip。 - 上游
main仍是434872a,PR MERGEABLE,无新冲突。
非阻塞项的去向
- spawn 后立即退出(如 bundled CLI 缺失)要到 ACP
initialize才暴露:已记在 ora-space/circus#466(2026-10-22 评论),PR 描述 Not covered 也写明。 tests/argv_test.ts只驱动session/new:同上记入 #466,描述写明。- 两处过时注释:本 commit 已修。
- PR 描述指向 ora-space/circus#467(ADR 转
implemented),并补了"令牌改在进程环境后,同一身份仍可读/proc/<pid>/environ,真实 Node 验收必须换身份"。 - specs
decisions/node/agent-runtime/20261017-plugin-owned-model-adapters.md在 specsupstream/main0434c82上仍是status: approved,与本 PR 声明的"部分实现、留给 #467"一致。
前提与未验证项(同前次)
- ora-space/circus#452 验收第 1 项(真实 Node 上换身份读进程参数)未做;第 3 项(OpenCode)未做。
- 真实 bundled 编译产物、Windows/macOS 的 argv 未测;仓库
.claude/settings.json覆盖只经过作者手工 fake 网关验证。
结论
增量正确,无阻塞意见。审过 head ad1b8ac0e80279c7ca686d93556282b1cac76c06,可以合并;合并后按 ora-space/circus#452、#467 收尾。
Summary
On a platform route the temporary token no longer appears on the Claude Code CLI's command line.
Cause. The route went through the adapter's
providers/set. The pinnedclaude-agent-acp0.94.0 builds one provider env (createEnvForProvider: the routing reset,ANTHROPIC_BASE_URL,ANTHROPIC_CUSTOM_HEADERSwithx-api-key: <token>) and puts it both in the CLI's process environment and in theenvof the programmatic settings. The Claude Agent SDK 0.3.303 serializes object settings into a--settings <json>argument, so the token was in/proc/<pid>/cmdlinefor anyone who can list processes.Fix. The route is split by who may read it:
.claude/settings.jsonand user settings, so neither can point the token at another base URL or restore another credential. That protection was the reason for usingproviders/set, and it is kept.ANTHROPIC_CUSTOM_HEADERScan only drop the token; the request still goes to the gateway and is refused there.providers/setis no longer sent. The adapter is on its route from spawn, soClaudeClient.readyisrunning. The bridge's own request, its 10-second timeout and its route-refusal classification are gone. A gateway that is down now fails the first turn instead ofagent/start, because the adapter no longer lists the gateway's models at start.ModelGuardkeeps refusingauthenticate,logout,auth/*andproviders/*, and keeps stripping the caller'smodel,env,extraArgsand executable options. A caller'ssettingsis replaced with the route's.env. The Node's child process handler (crates/plugin-lifecycle/src/childprocess.rsin ora-space/desktop) passes theenvto the process and does not log it.Tests.
tests/model_route_test.ts: the token is only inANTHROPIC_CUSTOM_HEADERSof the spawn environment and never in the session settings. Every session setup carries the route's settings, with or without caller options. A start sends nothing to the adapter. An Effect restart spawns on the same route.deno task test: 45 passed.tests/argv_test.ts(deno task test:argv, newargvCI job): runs the pinned adapter and CLI from npm at the versions inupstream.lock.jsonon a route. It fails if any process they start shows the token in its arguments. It also checks that the token is in the CLI's environment and that the base URL is in its--settings. Results on linux-arm64 with 0.94.0 / 0.3.303 / CLI 2.1.303:.claude/settings.jsonthat sets anotherANTHROPIC_BASE_URLgets no requests.Not covered here.
/proc/<pid>/environ, so this check needs a different identity.agent/startnow succeeds once the adapter is spawned, so an adapter that exits right away (for example a missing bundled CLI) only shows up atinitialize.tests/argv_test.tsdrivessession/newonly;session/load,session/resumeandsession/forkare covered by the unit tests.ADR
Implements part of
decisions/node/agent-runtime/20261017-plugin-owned-model-adapters.md(D4: the temporary token stays in memory or the execution's own environment) for this plugin. The ADR staysapproved: acceptance on a real Node and the OpenCode check are still open, and moving it toimplementedis ora-space/circus#467.The
--agent-configurationrelease switch is not touched.Refs ora-space/circus#481