fix: write apiKeyHelper to ~/.claude/settings.json for Claude Code v2.1.81+#1414
fix: write apiKeyHelper to ~/.claude/settings.json for Claude Code v2.1.81+#1414
Conversation
….1.81+ Claude Code v2.1.81+ reads apiKeyHelper from ~/.claude/settings.json instead of ~/.claude.json. The entrypoint now writes to both paths for backward/forward compatibility. Fixes: github/gh-aw#22713 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
Coverage comparison generated by |
|
@copilot please make sure that this fix does not compromise security. in other words, make sure that the actual api key is not shared with the agent. |
The fix does not expose the real API key to the agent. Here's why: What's written to the config files ( {"apiKeyHelper":"/usr/local/bin/get-claude-key.sh"}This is a path to a shell script, not the API key itself. What that script returns ( A placeholder value — never the real credential. Where the real key lives: Exclusively in the api-proxy sidecar container ( How auth actually works: Claude Code invokes the helper, gets the placeholder, sends requests to The dual-path write is purely a config pointer change; it does not alter what credentials the agent can access. |
Smoke Test Results
Overall: PASS
|
There was a problem hiding this comment.
Pull request overview
Updates the agent startup configuration so Claude Code v2.1.81+ can find apiKeyHelper in its new config location while preserving compatibility with older versions.
Changes:
- Refactors the Claude Code
apiKeyHelpersetup inentrypoint.shinto a helper function and writes it to both~/.claude.jsonand~/.claude/settings.json. - Updates an inline comment in
docker-manager.tsto document the dual-path behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
containers/agent/entrypoint.sh |
Writes apiKeyHelper to both the legacy and new Claude Code config paths. |
src/docker-manager.ts |
Comment update describing that both config paths are created by the entrypoint. |
Comments suppressed due to low confidence (2)
containers/agent/entrypoint.sh:193
settings.json(and potentially~/.claude.json) can contain multiple Claude settings. In the branch where the file exists but is missingapiKeyHelper, the script overwrites the entire JSON with{ "apiKeyHelper": ... }, which will delete any existing user settings. Consider parsing the existing JSON and updating/adding only theapiKeyHelperkey (e.g., via a small Node/Python helper), falling back to creating a new file only when the file does not exist or is empty/invalid.
echo "[entrypoint] $label exists but missing apiKeyHelper, writing..."
echo "{\"apiKeyHelper\":\"$CLAUDE_CODE_API_KEY_HELPER\"}" > "$config_file"
chmod 666 "$config_file"
echo "[entrypoint] ✓ Wrote apiKeyHelper to $label"
fi
else
echo "[entrypoint] Creating $label with apiKeyHelper..."
echo "{\"apiKeyHelper\":\"$CLAUDE_CODE_API_KEY_HELPER\"}" > "$config_file"
chmod 666 "$config_file"
containers/agent/entrypoint.sh:204
chmod 777 "$SETTINGS_DIR"makes~/.claudeworld-writable and, in chroot mode, will apply to the bind-mounted host~/.claudedirectory. This can weaken filesystem isolation on the host and allows unintended modification of Claude state/config. Prefer ensuring correct ownership (mapped UID/GID) and using a restrictive directory mode (typically 700/755) instead of 777.
mkdir -p "$SETTINGS_DIR"
chmod 777 "$SETTINGS_DIR" 2>/dev/null || true
write_api_key_helper "$SETTINGS_FILE" "$SETTINGS_FILE"
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if [ -f "$config_file" ]; then | ||
| if grep -q '"apiKeyHelper"' "$config_file"; then | ||
| CONFIGURED_HELPER=$(grep -o '"apiKeyHelper":"[^"]*"' "$config_file" | cut -d'"' -f4) | ||
| if [ "$CONFIGURED_HELPER" != "$CLAUDE_CODE_API_KEY_HELPER" ]; then | ||
| echo "[entrypoint][ERROR] apiKeyHelper mismatch in $label:" | ||
| echo "[entrypoint][ERROR] Environment variable: $CLAUDE_CODE_API_KEY_HELPER" | ||
| echo "[entrypoint][ERROR] Config file value: $CONFIGURED_HELPER" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
The grep -q '"apiKeyHelper"' check is looser than the subsequent extraction regex grep -o '"apiKeyHelper":"[^"]*"'. If the JSON is pretty-printed (e.g., whitespace around :) or uses escaped characters, the extraction can fail and, with set -e, may terminate the entrypoint unexpectedly. Consider using a JSON parser for both validation and update, or make the extraction robust to whitespace and handle parse failures by rewriting/updating the key instead of exiting due to a failed grep in a command substitution.
This issue also appears on line 185 of the same file.
containers/agent/entrypoint.sh
Outdated
| echo "{\"apiKeyHelper\":\"$CLAUDE_CODE_API_KEY_HELPER\"}" > "$config_file" | ||
| chmod 666 "$config_file" | ||
| echo "[entrypoint] ✓ Wrote apiKeyHelper to $label" | ||
| fi | ||
| echo "[entrypoint] ✓ Claude Code API key helper validated: $CLAUDE_CODE_API_KEY_HELPER" | ||
| else | ||
| # File exists but no apiKeyHelper - write it | ||
| echo "[entrypoint] Claude Code config file exists but missing apiKeyHelper, writing..." | ||
| echo "{\"apiKeyHelper\":\"$CLAUDE_CODE_API_KEY_HELPER\"}" > "$CONFIG_FILE" | ||
| chmod 666 "$CONFIG_FILE" | ||
| echo "[entrypoint] ✓ Wrote apiKeyHelper to $CONFIG_FILE" | ||
| echo "[entrypoint] Creating $label with apiKeyHelper..." | ||
| echo "{\"apiKeyHelper\":\"$CLAUDE_CODE_API_KEY_HELPER\"}" > "$config_file" | ||
| chmod 666 "$config_file" | ||
| echo "[entrypoint] ✓ Created $label with apiKeyHelper: $CLAUDE_CODE_API_KEY_HELPER" |
There was a problem hiding this comment.
chmod 666 on the config files makes them world-writable. Since these files control apiKeyHelper execution, leaving them writable by any user in the container is risky and also changes permissions on bind-mounted host files in chroot mode. Prefer chowning the file to the runtime user (awfuser / mapped UID) and using a least-privilege mode (e.g., 600/644 depending on whether Claude needs to update it).
This issue also appears on line 202 of the same file.
This comment has been minimized.
This comment has been minimized.
* Initial plan * fix: preserve existing claude config when merging apiKeyHelper Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/6efe3da1-78c0-44c2-b7ce-06b070ab91b8 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Smoke Test Results
Overall: PASS
|
This comment has been minimized.
This comment has been minimized.
…abled (#1419) * Initial plan * fix: exclude GITHUB_API_URL from agent container when api-proxy is enabled Regression introduced in commit 9761e87 (PR #1305) re-added an unconditional GITHUB_API_URL assignment that PR #1303 had fixed. Combined with the missing EXCLUDED_ENV_VARS entry for the --env-all code path, GITHUB_API_URL was leaking into the agent container when api-proxy is enabled. With GITHUB_API_URL present in the agent, the Copilot CLI routes its token exchange to api.github.com/copilot_internal/v2/token using the placeholder COPILOT_GITHUB_TOKEN instead of going through COPILOT_API_URL → api-proxy (which injects the real token), causing a 401 authentication failure. Fix: - Add GITHUB_API_URL to EXCLUDED_ENV_VARS when api-proxy is enabled (covers the --env-all path used by build-test.lock.yml) - Replace the duplicate unconditional assignment with the correct conditional version that was already present below it (from PR #1303) Tests: - should not leak GITHUB_API_URL to agent when api-proxy is enabled with envAll - should pass GITHUB_API_URL to agent when api-proxy is NOT enabled with envAll See: github/gh-aw#20875 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
|
Smoke Test Results — PASS
|
This comment has been minimized.
This comment has been minimized.
…led (#1420) * Initial plan * fix: restore GITHUB_API_URL in agent container when api-proxy is enabled Root cause: commit 5a56789 excluded GITHUB_API_URL from the agent container when api-proxy is enabled, based on the incorrect theory that its presence would cause the Copilot CLI to route token exchange through api.github.com with the placeholder token. Evidence from CI logs: successful runs (worktree-audit-observability) had GITHUB_API_URL present and worked; all failing runs lacked it and failed immediately with "Authentication failed" after ~1.2 seconds. The Copilot CLI needs GITHUB_API_URL to locate the GitHub API. Its Copilot-specific calls (token exchange, inference) already route through COPILOT_API_URL → api-proxy regardless of GITHUB_API_URL being set. Fix: - Remove EXCLUDED_ENV_VARS.add('GITHUB_API_URL') from api-proxy block - Change conditional GITHUB_API_URL assignment to unconditional - Update test to expect GITHUB_API_URL present even with api-proxy enabled Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/db4a1afe-02b9-43f2-bfae-41febbcbc8b5 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
|
Smoke test results — run 23512312431 ✅ GitHub MCP: PR #1420 "fix: restore GITHUB_API_URL in agent container when api-proxy is enabled", PR #1419 "fix: exclude GITHUB_API_URL from agent container when api-proxy is enabled" Overall: PASS
|
|
🔮 The ancient spirits stir; smoke omens are read.
Warning
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS
|
Smoke Test Results — PASS
Last 2 merged PRs:
PR author:
|
Chroot Version Comparison Results
Result: Not all runtimes matched —
|
Problem
Claude Code v2.1.81+ reads
apiKeyHelperfrom~/.claude/settings.json, but the AWF entrypoint only writes it to~/.claude.json(the legacy path). This causes Claude Code to reportapiKeySource: "none"at init, and all API calls fail withEHOSTUNREACH.Fixes: github/gh-aw#22713
Root Cause
The entrypoint writes the
apiKeyHelperconfig (which tells Claude Code how to retrieve the API key placeholder for credential isolation) to$HOME/.claude.json. Newer versions of Claude Code (v2.1.81+) read this setting from$HOME/.claude/settings.jsoninstead.Fix
Write
apiKeyHelperto both paths for backward/forward compatibility:~/.claude.json— legacy path (kept for older Claude Code versions)~/.claude/settings.json— new path (Claude Code v2.1.81+)The
~/.claudedirectory is already bind-mounted read-write (docker-manager.ts:760), sosettings.jsonis writable in both chroot and non-chroot modes.Changes
containers/agent/entrypoint.sh— Refactored the apiKeyHelper config block into a reusablewrite_api_key_helper()function, called for both the legacy and new pathssrc/docker-manager.ts— Updated comment to reflect dual-path behaviorTesting
main, unrelated to this change)~/.claudebind mount already exists, so no docker-compose changes needed