Migrating from claude-cli to ACP
How to move Claude Opus off the main-agent slot and onto an ACP escalation path after Anthropic blocked subscription OAuth from third-party harnesses in April 2026.
June 2026 note: This is now the ACPX compatibility runbook. For normal second-opinion code review, prefer Claude Code via tmux Relay, which keeps Claude Code in its first-party interactive harness and avoids
claude -p. The June notes observed print-mode automation drawing from Claude’s separate Usage bucket, so do not useclaude -pas the OpenClaw/Codex handoff path.
What Changed
In April 2026, Anthropic started rejecting Claude Max subscription OAuth tokens when used through third-party harnesses (OpenClaw, OpenRouter-style proxies, etc.). The official Claude Code CLI still works with Max, but only when Anthropic’s own client handles the handshake.
The concrete impact on an OpenClaw host:
- The bundled
anthropicplugin’sclaude-clibackend stopped authenticating. - Main-agent routing through
claude-cli/claude-opus-4-6now returnsUnknown modelor auth errors. - Any cron job, hook, or skill that assumed Opus was the primary model silently fails or falls back to another provider.
The April 2026 path forward was to keep Opus reachable via the Agent Client Protocol (ACP) through the ACPX plugin. Claude Code runs natively on your machine as an ACP server; OpenClaw connects to it as a client. Anthropic’s own CLI handles the Max OAuth handshake, and OpenClaw treats the resulting session as a sub-agent.
The June 2026 default is narrower: use the tmux relay for review and use ACPX only when you explicitly need an ACP endpoint.
Opus becomes an escalation target, not a primary orchestrator. Your main agent runs on something else (we use GPT 5.5 via Codex Pro - see multi-model orchestration).
Who This Is For
Anyone whose openclaw.json currently references:
anthropic/claude-*as the primary model, ORclaude-cli/claude-*as the primary model or a coder/researcher sub-agent, ORagents.defaults.cliBackends.claude-cliwith acommandpath.
If none of those apply, you’re already post-migration and can skip this guide.
Pre-Migration Checks
1. Confirm Claude Code is Installed Natively
ACPX shells out to the Claude Code CLI. Install it first if you haven’t:
# Official install
curl -fsSL https://claude.ai/install.sh | bash
# Or via npm
npm install -g @anthropic-ai/claude-code
# Confirm
claude --version
which claude
Note the absolute path (which claude). You’ll need it for the ACPX config.
2. Confirm Claude Code Can Authenticate
claude --help
# Run an interactive session once to complete OAuth
claude
Authenticate with your Max subscription. The session state persists in ~/.claude/. Once this works directly, ACPX will work.
3. Back Up Your Current Config
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.pre-acp.$(date +%F)
Also back up the four auth-profiles files (OpenClaw sometimes stores a claude-cli profile in each):
for f in ~/.openclaw/agents/{main,coder,builder}/agent/auth-profiles.json \
~/.openclaw/workspace/.openclaw/agents/main/agent/auth-profiles.json; do
[ -f "$f" ] && cp "$f" "$f.pre-acp.$(date +%F)"
done
Step 1: Install the ACPX Plugin
ACPX is distributed as a user-local binary. It is not part of the OpenClaw core install.
mkdir -p ~/.openclaw/vendor/acpx
cd ~/.openclaw/vendor/acpx
npm init -y >/dev/null
npm install @openclaw/acpx@latest
Confirm the binary path:
ls ~/.openclaw/vendor/acpx/node_modules/.bin/acpx
# Should print the path. This is what OpenClaw invokes.
Step 2: Register the Plugin in openclaw.json
Use config.patch for all edits below. Never config.apply unless you mean to replace the whole file.
2a. Add ACPX to plugins.allow
plugins.allow is an exclusive whitelist. Any plugin not listed is blocked, even bundled ones. Make sure ACPX is in:
{
"plugins": {
"allow": [
"telegram", "discord", "signal",
"google", "browser", "openai", "zai", "brave",
"memory-core", "tool-narration-guard", "content-scrubber",
"acpx"
]
}
}
2b. Register the Plugin Load Path
{
"plugins": {
"load": {
"paths": [
"/home/YOUR_USER/.openclaw/vendor/acpx/node_modules/@openclaw/acpx"
]
}
}
}
2c. Enable the Plugin Entry
{
"plugins": {
"entries": {
"acpx": {
"enabled": true,
"config": {
"command": "/absolute/path/to/claude",
"args": ["--permission-mode", "bypassPermissions"],
"input": "stdin"
}
}
}
}
}
Notes on the config block:
commandmust be the absolute path to the Claude Code binary. Under systemd,PATHis minimal; relative names likeclaudewon’t resolve.--permission-mode bypassPermissionspasses the CLI’s permission classifier where--dangerously-skip-permissionshard-blocks. Same practical effect, different enforcement path."input": "stdin"is required. Claude Code 2.1.92+ treats positional args as MCP config file paths when--strict-mcp-configis active, which breaks prompt passing.
Step 3: Remove the Old claude-cli Backend
3a. Drop the cliBackends Entry
{
"agents": {
"defaults": {
"cliBackends": {
"claude-cli": null
}
}
}
}
(Use config.patch with a null value to delete the key.)
3b. Remove claude-cli from the Primary/Fallback Chain
{
"agents": {
"defaults": {
"model": {
"primary": "openai-codex/gpt-5.5",
"fallbacks": [
"openai-codex/gpt-5.3-codex"
]
}
}
}
}
Replace the primary with whatever you’re standardizing on. Keep the fallback list short and confined to providers you actively use - fallback hops you haven’t tested will silently degrade quality when the primary hiccups.
3c. Delete claude-cli Auth Profiles
Subscription-OAuth profiles for anthropic:claude-cli are no longer usable. They can also cause fallback roulette if another provider’s rate limit trips. Remove them:
for f in ~/.openclaw/agents/{main,coder,builder}/agent/auth-profiles.json \
~/.openclaw/workspace/.openclaw/agents/main/agent/auth-profiles.json; do
[ -f "$f" ] && jq 'del(.profiles[] | select(.provider == "anthropic:claude-cli"))' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
done
3d. Remove the anthropic Plugin from plugins.allow (Optional)
If you’re not using any Anthropic-provided model path, drop it from the whitelist. If you’re keeping direct-API access for a specific escalation lane, leave it in.
Warning: Removing anthropic from plugins.allow also unregisters the claude-cli CLI backend symbol. If you later add claude-cli/... back to a model chain without re-enabling the plugin, you’ll get Unknown model: claude-cli/....
Step 4: Wire Opus as an ACP Escalation Target
4a. Register the ACP Agent
{
"agents": {
"list": [
{ "id": "main", "model": "openai-codex/gpt-5.5" },
{ "id": "coder", "model": "gpt55" },
{
"id": "acp-claude",
"model": "acpx/claude-opus-4-6",
"description": "Escalation target for intel, design, review, and academic work"
}
]
}
}
4b. Add a Discord Thread Pattern (Optional but Recommended)
Dedicate a Discord thread for direct Opus access. Posting in the thread routes straight to the ACP session, keeping Opus isolated from your main GPT 5.5 session:
{
"channels": {
"discord": {
"routing": {
"threads": {
"acp-opus": { "agentId": "acp-claude" }
}
}
}
}
}
Create the thread in Discord with the matching name (acp-opus or whatever you picked). The first message spins up the ACP session; subsequent messages continue it.
4c. Teach Your Main Agent When to Escalate
Update your AGENTS.md (or equivalent) with explicit escalation criteria. Mine:
## Escalate to ACP Opus (via acp-claude) when:
- Long-form reasoning over intel dossiers
- Design critique (architecture, visual systems, UX)
- PR review that needs taste, not just correctness
- Security review where failure-mode analysis matters
- USF academic work (all of it)
Do NOT escalate for:
- Code generation → coder (GPT 5.5)
- File scanning, grep, bulk ops → coder
- Email triage, cron output → main (GPT 5.5)
Without explicit criteria, GPT 5.5 either over-escalates (burns Opus quota) or never escalates (loses the quality win).
Step 5: Restart and Verify
systemctl --user restart openclaw-gateway
journalctl --user -u openclaw-gateway -n 50 --no-pager | grep -iE "acpx|claude|plugin"
You should see ACPX loading cleanly and no Unknown model: claude-cli/... errors.
Verification Commands
# ACPX plugin is allowed + loaded
jq '.plugins.allow | contains(["acpx"])' ~/.openclaw/openclaw.json
# The binary exists
test -x ~/.openclaw/vendor/acpx/node_modules/.bin/acpx && echo "✓ binary present"
# No stale claude-cli references
grep -r "claude-cli" ~/.openclaw/openclaw.json \
~/.openclaw/agents/*/agent/auth-profiles.json 2>/dev/null \
| grep -v "claude-cli.*null" || echo "✓ no stale claude-cli refs"
# Primary model is NOT on claude-cli
jq '.agents.defaults.model.primary' ~/.openclaw/openclaw.json
# Expected: a non-anthropic, non-claude-cli model string
Live Test
Open the Discord ACP thread and send a message that matches your escalation criteria. Watch logs:
journalctl --user -u openclaw-gateway -f | grep -iE "acpx|spawn|agent"
You should see the ACP session initialize, Claude Code launch as a child process, and the response stream back.
Rollback
If the migration breaks something:
cp ~/.openclaw/openclaw.json.pre-acp.<DATE> ~/.openclaw/openclaw.json
systemctl --user restart openclaw-gateway
If claude itself stopped working (Anthropic rolled out a new block), rollback doesn’t help - you need to move your primary off Claude regardless. That’s what this migration prepares you for.
What You Lose in the Move
Worth naming honestly:
- Opus no longer sees every incoming message. The main agent does. If you valued Opus’s injection resistance and nuance on every interaction, that goes away.
- ACP sessions are short-lived by design. Each invocation is a fresh Claude Code session. Cross-turn memory relies on your workspace files, not the model’s context.
- Response latency is higher. ACP adds a subprocess hop. Expect 1–3s overhead on top of normal model latency.
- Tool use is mediated twice. Claude Code has its own tool layer. OpenClaw wraps the ACP session. Complex tool chains can get tangled - keep escalation tasks narrow.
For most users, the trade is worth it: you preserve access to Opus for the tasks where it shines, and you stop fighting Anthropic’s subscription-OAuth policy.
Gotchas
-
OpenClaw upgrades can reset plugin config. Every minor upgrade across the 2026.4.x line has silently dropped custom directives from the systemd unit and (less reliably) reset
plugins.entries. After any upgrade, re-verifyjq '.plugins.entries.acpx' ~/.openclaw/openclaw.jsonandgrep EnvironmentFile ~/.config/systemd/user/openclaw-gateway.service. -
--strict-mcp-configsemantics flipped. Under the new Claude Code, positional args are MCP config paths, not prompts. Always use"input": "stdin"in the ACPX config. -
--permission-mode bypassPermissionsvs.--dangerously-skip-permissions. The flag names sound different but do the same thing at runtime. Bypass passes the harness’s pre-exec classifier where the dangerous one gets caught. Use bypass. -
The Claude Code OAuth token is in
~/.claude/, not OpenClaw’s config. Don’t try to copy it intoauth-profiles.json. ACPX launches Claude Code as a subprocess and inherits its auth state. -
CLI backend warmup warnings are cosmetic. OpenClaw logs a warmup failure for
claude-cli/*models at startup if any are referenced anywhere (including dead fallback chains). Clean up the references; don’t try to silence the warning at the log layer. -
Don’t put ACP Opus in the primary/fallback chain. It’s an escalation target, invoked by agent ID. The fallback chain is for cases where the primary provider is down. Opus via ACP is slower and meant for specific task shapes, not a generic fallback.
-
Anthropic may block the ACP path too. The Max subscription’s terms let them update the client-detection heuristic anytime. If the ACP escalation also breaks, your options narrow to direct Anthropic API billing (pay-as-you-go) or moving fully off Claude. Keep the rest of your stack resilient so Opus remains optional.