Skip to content

fix: get-claude-key.sh missing inside chroot — apiKeyHelper exit 127 #1507

@lpcox

Description

@lpcox

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

  1. Dockerfile copies get-claude-key.sh/usr/local/bin/get-claude-key.sh (inside the image) ✅
  2. docker-manager.ts:1455 sets CLAUDE_CODE_API_KEY_HELPER=/usr/local/bin/get-claude-key.sh
  3. entrypoint.sh:149-221 writes apiKeyHelper config to .claude.json and .claude/settings.json pointing to /usr/local/bin/get-claude-key.sh
  4. Chroot mode: docker-manager.ts:768 bind-mounts /usr:/host/usr:ro — the host's /usr replaces the container's /usr in the chroot filesystem
  5. Inside chroot, /usr/local/bin/get-claude-key.shhost's /usr/local/bin/get-claude-key.shdoes not exist
  6. Claude Code finds the apiKeyHelper config but the script fails with exit 127 (not found)
  7. 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
fi

2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions