-
Notifications
You must be signed in to change notification settings - Fork 18
fix: get-claude-key.sh missing inside chroot — apiKeyHelper exit 127 #1507
Description
Problem
get-claude-key.sh is baked into the Docker image at /usr/local/bin/get-claude-key.sh (via COPY in containers/agent/Dockerfile:83), but is not accessible inside the chroot because the chroot bind-mounts the host's /usr over the container's /usr.
How it fails
Dockerfilecopiesget-claude-key.sh→/usr/local/bin/get-claude-key.sh(inside the image) ✅docker-manager.ts:1455setsCLAUDE_CODE_API_KEY_HELPER=/usr/local/bin/get-claude-key.sh✅entrypoint.sh:149-221writesapiKeyHelperconfig to.claude.jsonand.claude/settings.jsonpointing to/usr/local/bin/get-claude-key.sh✅- Chroot mode:
docker-manager.ts:768bind-mounts/usr:/host/usr:ro— the host's/usrreplaces the container's/usrin the chroot filesystem - Inside chroot,
/usr/local/bin/get-claude-key.sh→ host's/usr/local/bin/get-claude-key.sh→ does not exist ❌ - Claude Code finds the
apiKeyHelperconfig but the script fails with exit 127 (not found) - All API calls fail with
EHOSTUNREACH— zero tokens consumed
apiKeyHelper failed: exited 127: /bin/sh: 1: /usr/local/bin/get-claude-key.sh: not found
Why one-shot-token.so works but get-claude-key.sh doesn't
one-shot-token.so has the exact same problem but was already fixed (entrypoint.sh:406-427): it's explicitly copied from the container's /usr/local/lib/one-shot-token.so to /host/tmp/awf-lib/one-shot-token.so before the chroot happens. The chroot then sees it at /tmp/awf-lib/one-shot-token.so.
get-claude-key.sh has no equivalent copy step.
Root Cause
The chroot overlay means any file baked into the Docker image under /usr/ is shadowed by the host's /usr/ bind mount. Files that need to be accessible inside the chroot must be explicitly copied to a writable path (like /tmp/) before chroot activation.
Proposed Fix
Follow the one-shot-token.so pattern — copy get-claude-key.sh to /host/tmp/awf-lib/ before chroot, and update the apiKeyHelper config path accordingly.
1. In containers/agent/entrypoint.sh (chroot section, near line 406)
Add a copy step for get-claude-key.sh:
# Copy get-claude-key.sh to chroot-accessible path
# The script is in the Docker image at /usr/local/bin/ but the chroot
# bind-mounts the host's /usr, shadowing the container's copy.
if [ -n "$CLAUDE_CODE_API_KEY_HELPER" ] && [ -f "$CLAUDE_CODE_API_KEY_HELPER" ]; then
if mkdir -p /host/tmp/awf-lib 2>/dev/null; then
CHROOT_KEY_HELPER="/tmp/awf-lib/get-claude-key.sh"
if cp "$CLAUDE_CODE_API_KEY_HELPER" "/host${CHROOT_KEY_HELPER}" 2>/dev/null && \
chmod +x "/host${CHROOT_KEY_HELPER}" 2>/dev/null; then
CLAUDE_CODE_API_KEY_HELPER="$CHROOT_KEY_HELPER"
echo "[entrypoint] Claude key helper copied to chroot at ${CHROOT_KEY_HELPER}"
else
echo "[entrypoint][WARN] Could not copy get-claude-key.sh to chroot"
fi
fi
fi2. Update apiKeyHelper config to use the chroot-accessible path
The apiKeyHelper config writing (lines 149-221) must use the updated path. This means the copy step should run before the config writing, or the config writing should check for the chroot-corrected path.
3. Consider a general pattern
Any script baked into the image under /usr/ that needs chroot access should use this pattern. A helper function could centralize this:
copy_to_chroot() {
local src="$1" dst_name="$2"
local dst="/tmp/awf-lib/${dst_name}"
if [ -f "$src" ]; then
mkdir -p /host/tmp/awf-lib 2>/dev/null
if cp "$src" "/host${dst}" 2>/dev/null && chmod +x "/host${dst}" 2>/dev/null; then
echo "$dst"
return 0
fi
fi
return 1
}Affected Versions
- AWF v0.25.1 through v0.25.4 (chroot mode with Claude engine)
- Non-chroot mode is unaffected (the container's
/usr/local/bin/is not shadowed)
References
- Upstream report: engine: claude — get-claude-key.sh missing in chroot (AWF v0.25.4), apiKeyHelper exit 127 gh-aw#23614
- Related (config path fix): engine: claude — ANTHROPIC_API_KEY not reaching Claude Code CLI inside sandbox (apiKeySource: none) gh-aw#22713 / gh-aw-firewall#1414
get-claude-key.shsource:containers/agent/get-claude-key.sh- Dockerfile COPY:
containers/agent/Dockerfile:83 - Env var set:
src/docker-manager.ts:1455 - Config writing:
containers/agent/entrypoint.sh:149-221 one-shot-token.socopy pattern:containers/agent/entrypoint.sh:406-427- Chroot
/usrbind mount:src/docker-manager.ts:768