Skip to content

Dockerfile.claude installs @anthropic-ai/claude-code but claude-agent-acp adapter never uses it #418

@brettchien

Description

@brettchien

Description

Dockerfile.claude installs @anthropic-ai/claude-code globally via npm and carefully pins CLAUDE_CODE_VERSION=2.1.104 (PR #333, reaffirmed in PR #412) to dodge the 2.1.105+ auth regression.

However, claude-agent-acp@0.25.0+ does not invoke the globally installed /usr/local/bin/claude at runtime. The adapter shells out to the cli.js bundled inside its own node_modules/@anthropic-ai/claude-agent-sdk/cli.js:

// claude-agent-acp/dist/acp-agent.js:34
export async function claudeCliPath() {
    return isStaticBinary()
        ? (await import("@anthropic-ai/claude-agent-sdk/embed")).default
        : import.meta.resolve("@anthropic-ai/claude-agent-sdk").replace("sdk.mjs", "cli.js");
}

isStaticBinary() is only true when CLAUDE_AGENT_ACP_IS_SINGLE_FILE_BUN is set (bun single-file build), which openab never sets. So the non-static branch always wins and the adapter uses the SDK-bundled cli.js, not the globally installed @anthropic-ai/claude-code.

Consequence: pinning CLAUDE_CODE_VERSION in Dockerfile.claude has no effect on what Claude CLI actually runs inside sessions. The claude-code version that matters is the one claude-agent-sdk bundles, which is implicitly chosen by the claude-agent-acp version.

This is not a recent regression — the adapter has never used the globally installed @anthropic-ai/claude-code for normal (non-static) npm installs. Timeline verified against the adapter's git tags (src/acp-agent.ts):

claude-agent-acp version Released pathToClaudeCodeExecutable behavior
v0.2.x (Feb 2025–Sep 2025) Field not set at all; SDK default (bundled cli.js) applies unconditionally
v0.6.0 2025-10-15 Introduced CLAUDE_CODE_EXECUTABLE env override; still SDK-default when env var unset
v0.7.0 – v0.18.0 2025-10-27 → 2026-02-18 Same behavior as v0.6.0
v0.19.0 2026-02-27 Added isStaticBinary() + claudeCliPath() helper for bun single-file builds only; non-static npm installs still pass {} → SDK default
v0.20.x – v0.28.0 2026-03-03 → 2026-04-15 Same logic as v0.19.0
v0.29.0 (current) 2026-04-16 Same logic

In every version, openab's integration (npm install inside a Debian container, no CLAUDE_AGENT_ACP_IS_SINGLE_FILE_BUN, no CLAUDE_CODE_EXECUTABLE) falls through to the SDK default, which is the SDK's own bundled cli.js. The globally installed /usr/local/bin/claude has been dead code in Dockerfile.claude since the Dockerfile was first authored.

By extension, PR #333's rationale ("revert to 2.1.104 due to 2.1.105+ auth regression in the TUI paste flow") is based on a false premise: the adapter never runs the system claude binary's /login TUI. The auth regression it cited (anthropics/claude-code#47648) cannot affect openab sessions. Same for PR #412's "stay on 2.1.104" reasoning about the parallel-mkdir race in 2.1.112 — that race is in the claude-code binary's runtime, which the adapter doesn't invoke.

Concrete evidence: compare Opus 4.7 knowledge between the two install locations in the same container (adapter 0.29.0 + CLAUDE_CODE_VERSION=2.1.104):

cli.js location Knows Opus 4.7?
node_modules/@agentclientprotocol/claude-agent-acp/node_modules/@anthropic-ai/claude-agent-sdk/cli.js (what adapter actually runs) ✅ yes — Opus 4.7, claude-opus-4-7 both present
/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js (globally installed, in PATH, unused by adapter) ❌ no — only up to Opus 4.6, claude-opus-4-6

Despite the global install reporting up to Opus 4.6 only, an openab session running adapter 0.29.0 correctly returns availableModels containing Opus 4.7 and accepts currentModelId: "opus" as 4.7.


Steps to Reproduce

Build the current Dockerfile.claude at main and inspect the two cli.js files inside the container:

docker build -f Dockerfile.claude -t openab-claude:test .
docker run --rm --entrypoint sh openab-claude:test -c '
  SDK_DIR=$(find /usr/local/lib/node_modules -name claude-agent-sdk -type d | head -1)
  echo "adapter-bundled cli.js:"
  grep -oE "claude-opus-4-[0-9]+" "$SDK_DIR/cli.js" | sort -u
  echo "globally installed cli.js:"
  grep -oE "claude-opus-4-[0-9]+" /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js | sort -u
'

The two lists differ — proving the global install is out-of-sync with the one the adapter actually uses.

Additionally, trace which binary is spawned when openab opens a Claude session:

# In a running openab-claude container with a session open:
ps auxf | grep -i claude
lsof -p $(pgrep -f claude-agent-acp) | grep cli.js

The cli.js being read is under @agentclientprotocol/claude-agent-acp/node_modules/@anthropic-ai/claude-agent-sdk/ — never /usr/local/lib/node_modules/@anthropic-ai/claude-code/.


Expected Behavior

One of the following should hold:

  1. Drop @anthropic-ai/claude-code from Dockerfile.claude — if nothing in the container actually relies on it, the install is pure bloat (~50 MB image) and its pinning effort is cargo-cult.
  2. Or wire it in explicitly — set CLAUDE_CODE_EXECUTABLE=/usr/local/bin/claude in the container env (adapter checks this env var first per acp-agent.js:1079) so the pinned version is what the adapter actually runs. Then CLAUDE_CODE_VERSION becomes load-bearing again.
  3. Document the current behavior — add a comment in Dockerfile.claude explaining that @anthropic-ai/claude-code is installed only for claude --version debugging / auth tooling, and that the real Claude CLI used by sessions is the one bundled in claude-agent-acp. This saves future maintainers from repeating the PR fix: revert claude-code to 2.1.104 due to upstream auth regression #333 / fix: bump CLI versions — codex 0.121.0, gemini 0.38.1, copilot 1.0.30, cursor 2026.04.15 #412 analysis believing it's load-bearing when it isn't.

Option (2) is the most correct if reproducibility is the goal, since it decouples adapter-bundled sdk version from the CLI version.


Environment

  • Chart: openab 0.7.7
  • Image: ghcr.io/openabdev/openab-claude:0.7.7 (and verified in local build of Dockerfile.claude at main commit de9bce4)
  • Adapter: @agentclientprotocol/claude-agent-acp@0.25.0 (Dockerfile default; 0.29.0 in my local repro)
  • CLI: @anthropic-ai/claude-code@2.1.104
  • Runtime: Docker 28.x on OrbStack / Linux container, node:22-bookworm-slim base

Screenshots / Logs

acp-agent.js:34 (adapter source) — see Description section.

Grep output from my running container (adapter 0.29.0 + CLI 2.1.104):

adapter-bundled cli.js: claude-opus-4-0, claude-opus-4-1, claude-opus-4-5, claude-opus-4-6, claude-opus-4-7
globally installed cli.js: claude-opus-4-0, claude-opus-4-1, claude-opus-4-5, claude-opus-4-6

ACP session/new response from the same pod showing Opus 4.7 available despite global install being 2.1.104:

{
  "modelId": "opus",
  "name": "Opus",
  "description": "Opus 4.7 · Most capable for complex work"
}

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingp1High — address this sprint

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions