Skip to content

fix(docker): resolve Claude binary to glibc variant on Debian image#1521

Merged
leex279 merged 4 commits intodevfrom
archon/thread-2a37cd6b
May 1, 2026
Merged

fix(docker): resolve Claude binary to glibc variant on Debian image#1521
leex279 merged 4 commits intodevfrom
archon/thread-2a37cd6b

Conversation

@leex279
Copy link
Copy Markdown
Collaborator

@leex279 leex279 commented May 1, 2026

Summary

  • Problem: All Claude SDK calls fail in Docker — CLAUDE_BIN_PATH pointed to the SDK 0.1.x cli.js path which no longer exists in SDK 0.2.x; and even without that env var, the SDK's resolver picks the musl binary first, which cannot execute on the Debian glibc base image.
  • Why it matters: Blocker for all Docker users on current dev — Claude is the default provider and is completely non-functional.
  • What changed: Removed the stale ENV CLAUDE_BIN_PATH=.../cli.js from Dockerfile; added runtime CPU arch detection in docker-entrypoint.sh that pins CLAUDE_BIN_PATH to the correct glibc package (linux-x64 for x86_64, linux-arm64 for aarch64) before starting the server.
  • What did not change: No application code changes; binary-resolver.ts already handles CLAUDE_BIN_PATH correctly. Users can still override via .env or docker run -e CLAUDE_BIN_PATH=....

UX Journey

Before

User                          Archon (Docker)               Claude SDK
────                          ───────────────                ──────────
POST /message ──────────────▶ orchestrator-agent
                              sendQuery() ────────────────▶ resolveClaudeBinaryPath
                                                            reads CLAUDE_BIN_PATH
                                                            (.../cli.js — does not exist)
                              error logged ◀───────────── throws "file does not exist"
500 ◀──────────────────────── orchestrator_message_failed

After removing stale ENV — SDK resolver picks musl binary:
                              sendQuery() ────────────────▶ SDK auto-resolves
                                                            picks musl variant first
                                                            spawn(<musl claude>)
                                                            [X] cannot execute (glibc host)
                              query_error ◀───────────── ENOENT (loader missing)

After

User                          Archon (Docker)               Claude SDK
────                          ───────────────                ──────────
[entrypoint sets CLAUDE_BIN_PATH=/app/.../claude-agent-sdk-linux-{x64|arm64}/claude]
POST /message ──────────────▶ orchestrator-agent
                              sendQuery() ────────────────▶ resolveClaudeBinaryPath
                                                            reads CLAUDE_BIN_PATH (glibc binary)
                                                            [✓] file exists, source=env
                              streams response ◀────────── Claude executes
reply ◀──────────────────────  sends to platform

Architecture Diagram

Before

Dockerfile
  └── ENV CLAUDE_BIN_PATH=.../claude-agent-sdk/cli.js   [stale — SDK 0.1.x path]

docker-entrypoint.sh
  └── exec gosu appuser bun run start

binary-resolver.ts
  └── reads CLAUDE_BIN_PATH → file missing → throws

After

Dockerfile
  └── (comment only — no ENV CLAUDE_BIN_PATH)           [~]

docker-entrypoint.sh
  └── if [ -z "$CLAUDE_BIN_PATH" ]; then                [+]
        uname -m → x86_64|aarch64
        export CLAUDE_BIN_PATH=.../linux-x64|arm64/claude
      fi
  └── exec gosu appuser bun run start

binary-resolver.ts (unchanged)
  └── reads CLAUDE_BIN_PATH → glibc binary → returns path

Connection inventory:

From To Status Notes
Dockerfile image ENV modified Removed stale CLAUDE_BIN_PATH
docker-entrypoint.sh CLAUDE_BIN_PATH env var new Runtime arch detection, respects user override
binary-resolver.ts CLAUDE_BIN_PATH unchanged Already reads env var correctly

Label Snapshot

  • Risk: risk: low
  • Size: size: XS
  • Scope: ci
  • Module: docker:entrypoint

Change Metadata

  • Change type: bug
  • Primary scope: ci

Linked Issue

Validation Evidence (required)

bun run validate

No TypeScript/JS code changed — only Dockerfile (comment replacement) and docker-entrypoint.sh (shell script). Type-check, lint, format, and test suites are unaffected. Docker build validation requires a live Docker daemon.

The fix follows the same pattern described in issue #1519 and verified by the issue author on linux/arm64. This PR extends that fix to handle x86_64→x64 arch mapping correctly (the issue's suggested ARG TARGETARCH approach would have silently produced a wrong path on amd64 builds).

  • Evidence provided: Issue author verified glibc binary executes successfully; claude.binary_resolved log shows source=env
  • Intentionally skipped: Docker build smoke test (no Docker daemon in CI)

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No

Compatibility / Migration

  • Backward compatible? Yes — users who already set CLAUDE_BIN_PATH in .env are unaffected (the entrypoint skips the auto-detection when CLAUDE_BIN_PATH is already set)
  • Config/env changes? No — existing .env files work unchanged
  • Database migration needed? No

Human Verification (required)

What was personally validated beyond CI:

  • Verified scenarios: Traced the execution path in binary-resolver.ts — env var is read at step 1, before BUNDLED_IS_BINARY check, so it works in both source and binary mode
  • Edge cases checked: User-provided CLAUDE_BIN_PATH in .env is preserved; unknown arch (e.g. riscv64) silently skips the auto-detection, falling through to SDK's default resolution
  • What was not verified: Live Docker build and container run (no Docker daemon available in this environment)

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: Docker deployment only — no impact on local dev, CLI, or platform adapters
  • Potential unintended effects: If a future SDK version changes the binary location within its optional-dep package, the hardcoded path would need updating (same maintenance burden as before)
  • Guardrails/monitoring for early detection: binary-resolver.ts logs claude.binary_resolved with source=env and the resolved path — visible in container logs

Rollback Plan (required)

  • Fast rollback command/path: Revert this commit; set CLAUDE_BIN_PATH manually in .env as a workaround
  • Feature flags or config toggles: Users can override CLAUDE_BIN_PATH in .env at any time
  • Observable failure symptoms: orchestrator_message_failed / title.generate_failed in container logs; CLAUDE_BIN_PATH is set to "..." but the file does not exist error

Risks and Mitigations

  • Risk: Hardcoded path breaks if SDK reorganizes its package structure in a future version
    • Mitigation: Path is set only as a default; the binary-resolver.ts file-existence check will surface a clear error immediately if the path becomes stale; users can override via CLAUDE_BIN_PATH

Summary by CodeRabbit

  • Bug Fixes

    • Container startup now auto-selects and exports an architecture-appropriate executable, improving libc compatibility and preventing runtime failures when a pinned binary is present.
  • Documentation

    • Clarified that the executable path is determined at container startup (not at build time) and added a runtime error message for unsupported architectures or missing/non-executable pinned binaries.

Bun's hoisted linker installs both glibc and musl optional-dep packages
for the detected CPU arch. The SDK's resolver picks musl first, which
fails to execute on the Debian (glibc) base image — the musl dynamic
loader is absent, causing every Claude call to fail.

Remove the stale ENV CLAUDE_BIN_PATH pointing to the SDK 0.1.x cli.js
path (no longer present in SDK 0.2.x), and add runtime arch detection
in docker-entrypoint.sh. The entrypoint maps uname -m output to the
correct glibc package suffix (x86_64→linux-x64, aarch64→linux-arm64)
and exports CLAUDE_BIN_PATH before exec-ing the server. Users can still
override via CLAUDE_BIN_PATH in their .env or docker run -e.

Closes #1519

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5000905f-edb4-4973-a05b-71d56907bd01

📥 Commits

Reviewing files that changed from the base of the PR and between 141ec91 and 1927c9c.

📒 Files selected for processing (1)
  • docker-entrypoint.sh

📝 Walkthrough

Walkthrough

Removed the build-time CLAUDE_BIN_PATH Docker ENV and added runtime logic in docker-entrypoint.sh that detects CPU architecture (uname -m), selects an architecture-specific Claude binary if not already set, exports CLAUDE_BIN_PATH, and then runs bun run setup-auth and bun run start.

Changes

Cohort / File(s) Summary
Docker Build Configuration
Dockerfile
Removed static CLAUDE_BIN_PATH build-time ENV and documented that the entrypoint will define it at container startup.
Container Entrypoint Logic
docker-entrypoint.sh
Added startup detection of uname -m and conditional export of CLAUDE_BIN_PATH to architecture-specific Claude binary candidates (x86_64 → x64 path, aarch64 → arm64 path); validates candidate exists and is executable, errors on unsupported arch or missing binary, otherwise proceeds to existing setup and start commands.

Sequence Diagram(s)

sequenceDiagram
    participant Docker as Container runtime
    participant Entrypoint as docker-entrypoint.sh
    participant FS as Filesystem (binaries)
    participant Bun as Bun (setup/start)

    Docker->>Entrypoint: start container
    Entrypoint->>Entrypoint: check CLAUDE_BIN_PATH env
    alt CLAUDE_BIN_PATH not set
        Entrypoint->>Entrypoint: run `uname -m` -> arch
        Entrypoint->>FS: check candidate binary for arch
        alt candidate exists & executable
            Entrypoint->>Entrypoint: export CLAUDE_BIN_PATH
        else candidate missing/non-exec
            Entrypoint->>Docker: write error and exit
        end
    else CLAUDE_BIN_PATH set
        Entrypoint->>Entrypoint: keep provided CLAUDE_BIN_PATH
    end
    Entrypoint->>Bun: bun run setup-auth
    Bun->>Bun: setup completes
    Entrypoint->>Bun: bun run start
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

Poem

🐰
I sniff the arch at break of light,
pick the bin that fits just right.
I set the path, then bounce away —
bun wakes up and starts the day. 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately identifies the primary bug fix: resolving the Claude binary to the glibc variant on the Debian Docker image, which directly addresses the blocker issue.
Description check ✅ Passed The description is comprehensive and well-structured, covering all major template sections including summary, UX journey, architecture diagrams, labels, validation evidence, security impact, compatibility, human verification, side effects, rollback plan, and risks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch archon/thread-2a37cd6b

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docker-entrypoint.sh`:
- Around line 45-49: The case block that sets CLAUDE_BIN_PATH based on uname -m
needs a default branch to fail fast on unsupported architectures and validate
the chosen binary exists: add a *) default in the case for docker-entrypoint.sh
that prints an explicit error mentioning the detected "$(uname -m)" and exits
non-zero, and after setting CLAUDE_BIN_PATH (in the x86_64/aarch64 branches) add
a test like [ -x "$CLAUDE_BIN_PATH" ] or [ -f "$CLAUDE_BIN_PATH" ] || { echo
"error: missing or non-executable $CLAUDE_BIN_PATH" >&2; exit 1; } so the script
errors clearly if the binary is absent or the architecture is unsupported.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1a814ae-6b15-4662-ad8b-52f73df51e1e

📥 Commits

Reviewing files that changed from the base of the PR and between 4631b8e and f810177.

📒 Files selected for processing (2)
  • Dockerfile
  • docker-entrypoint.sh

Comment thread docker-entrypoint.sh
Add a *)  fallback branch so operators on unsupported architectures
(riscv64, ppc64le, etc.) get an explicit warning at startup rather than
a silent no-op followed by a cryptic SDK error later.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…BIN_PATH

Add a file-existence check after the uname -m arch detection so a missing
or renamed binary in a future SDK version emits a clear WARN at startup
rather than letting the container start cleanly and failing silently on
first Claude invocation.

Follows the project's Fail Fast principle: surface the problem as early
as possible with an actionable message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
docker-entrypoint.sh (1)

49-56: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast here instead of warning-and-continuing.

Line 49 and Line 55 currently continue startup after warning, which reintroduces delayed runtime failure; and Line 52 checks -f (exists) instead of -x (executable). Exit non-zero in these cases so startup fails clearly.

Proposed hardening patch
 if [ -z "${CLAUDE_BIN_PATH:-}" ]; then
   case "$(uname -m)" in
-    x86_64)  _CLAUDE_BIN_CANDIDATE="/app/node_modules/@anthropic-ai/claude-agent-sdk-linux-x64/claude" ;;
-    aarch64) _CLAUDE_BIN_CANDIDATE="/app/node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64/claude" ;;
-    *) echo "WARN: Unsupported CPU architecture $(uname -m). Set CLAUDE_BIN_PATH manually if Claude fails to start." >&2 ;;
+    x86_64|amd64) _CLAUDE_BIN_CANDIDATE="/app/node_modules/@anthropic-ai/claude-agent-sdk-linux-x64/claude" ;;
+    aarch64|arm64) _CLAUDE_BIN_CANDIDATE="/app/node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64/claude" ;;
+    *)
+      echo "ERROR: Unsupported CPU architecture $(uname -m). Set CLAUDE_BIN_PATH manually." >&2
+      exit 1
+      ;;
   esac
   if [ -n "${_CLAUDE_BIN_CANDIDATE:-}" ]; then
-    if [ -f "$_CLAUDE_BIN_CANDIDATE" ]; then
+    if [ -x "$_CLAUDE_BIN_CANDIDATE" ]; then
       export CLAUDE_BIN_PATH="$_CLAUDE_BIN_CANDIDATE"
     else
-      echo "WARN: Pinned Claude binary not found at ${_CLAUDE_BIN_CANDIDATE}. SDK will use its default resolver — Claude may fail to start." >&2
+      echo "ERROR: Claude binary missing or non-executable at ${_CLAUDE_BIN_CANDIDATE}" >&2
+      exit 1
     fi
   fi
 fi
#!/bin/bash
set -euo pipefail

file="docker-entrypoint.sh"

echo "Claude pinning block:"
nl -ba "$file" | sed -n '45,58p'

echo
echo "Within this block, verify unsupported-arch handling and exit behavior:"
awk 'NR>=45 && NR<=58 && /Unsupported CPU architecture|exit [0-9]+/' "$file"

echo
echo "Within this block, verify candidate check uses executable bit:"
awk 'NR>=45 && NR<=58 && /\[ -[fx] /' "$file"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-entrypoint.sh` around lines 49 - 56, The startup currently warns and
continues on unsupported CPU or missing/non-executable pinned binary; change the
behavior in the CLAUDE pinning block so that when the unsupported-architecture
case is hit it exits with non-zero, and when _CLAUDE_BIN_CANDIDATE is set but
either missing or not executable use [ -x "$_CLAUDE_BIN_CANDIDATE" ] and exit
non-zero with a clear error message instead of only warning; ensure
CLAUDE_BIN_PATH is only exported when the candidate exists and is executable
(symbol references: _CLAUDE_BIN_CANDIDATE, CLAUDE_BIN_PATH and the Unsupported
CPU architecture case).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docker-entrypoint.sh`:
- Around line 49-56: The startup currently warns and continues on unsupported
CPU or missing/non-executable pinned binary; change the behavior in the CLAUDE
pinning block so that when the unsupported-architecture case is hit it exits
with non-zero, and when _CLAUDE_BIN_CANDIDATE is set but either missing or not
executable use [ -x "$_CLAUDE_BIN_CANDIDATE" ] and exit non-zero with a clear
error message instead of only warning; ensure CLAUDE_BIN_PATH is only exported
when the candidate exists and is executable (symbol references:
_CLAUDE_BIN_CANDIDATE, CLAUDE_BIN_PATH and the Unsupported CPU architecture
case).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a18846b5-8a59-42b6-8bf1-8f898e1ff650

📥 Commits

Reviewing files that changed from the base of the PR and between c36e49b and 141ec91.

📒 Files selected for processing (1)
  • docker-entrypoint.sh

The arch-detection block warned and continued in two failure modes —
unsupported CPU and missing pinned binary — which left CLAUDE_BIN_PATH
unset and silently fell through to the SDK's musl-first resolver, the
exact bug this fix targets. Exit non-zero in both cases so startup
surfaces a clear error instead of a delayed runtime failure.

Also switch the existence check from -f to -x (the next thing we do
with the path is execute it) and unset the helper var so it doesn't
leak into the child process environment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@leex279 leex279 merged commit 69b2c89 into dev May 1, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docker: Claude binary fails to resolve on glibc image (SDK 0.2.x picks musl variant)

1 participant