Skip to content

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

Closed

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.sh → host's /usr/local/bin/get-claude-key.sh → does 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

Activity

  1. added a commit that references this issue on Mar 31, 2026
    48a4f1a
  2. added a commit that references this issue on Mar 31, 2026
    2aff0f3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions