Skip to content

Comments

fix: remove .claude.json file bind mount regression#911

Merged
Mossaka merged 2 commits intomainfrom
fix/claude-json-bind-mount
Feb 17, 2026
Merged

fix: remove .claude.json file bind mount regression#911
Mossaka merged 2 commits intomainfrom
fix/claude-json-bind-mount

Conversation

@Mossaka
Copy link
Collaborator

@Mossaka Mossaka commented Feb 16, 2026

Summary

  • Remove the .claude.json file bind mount added in v0.18.0 that prevents Claude Code from writing its config
  • File bind mounts on Linux prevent atomic writes (rename() returns EBUSY), breaking Claude Code which uses temp file + rename
  • The writable home volume already provides a writable $HOME, and entrypoint.sh already creates .claude.json from CLAUDE_CODE_API_KEY_HELPER

Root Cause

v0.18.0 added code at src/docker-manager.ts:552-568 that:

  1. Creates ~/.claude.json on the host (writes {})
  2. Bind-mounts it as a file into the container: ${claudeJsonPath}:/host${claudeJsonPath}:rw

This is unnecessary because:

  • The writable home volume (${config.workDir}-chroot-home:/host${effectiveHome}:rw) already provides a writable $HOME
  • entrypoint.sh (lines 126-163) already creates .claude.json with apiKeyHelper content from CLAUDE_CODE_API_KEY_HELPER env var
  • No host content needs to be preserved — the file is generated fresh each run

What Changed

Removed the entire .claude.json bind mount block (17 lines deleted):

  • Host file creation (fs.writeFileSync(claudeJsonPath, '{}'))
  • File bind mount (agentVolumes.push(...))

Added an explanatory comment (4 lines) documenting why this mount is intentionally omitted.

No other files changedentrypoint.sh already handles the "file does not exist" case correctly.

Test plan

  • npm run build — TypeScript compiles cleanly
  • npm test — 794 tests pass, 0 failures
  • npm run lint — 0 errors (274 pre-existing warnings)
  • Manual: verify entrypoint creates .claude.json on writable volume (not bind mount)
  • Manual: verify Claude Code can do atomic writes to .claude.json
  • Manual: verify Claude Code still works behind API proxy

Fixes github/gh-aw#16214

🤖 Generated with Claude Code

File bind mounts on Linux prevent atomic writes (temp file + rename()
returns EBUSY). Claude Code uses atomic writes for ~/.claude.json config,
causing silent failure with 0 tool calls. The writable home volume already
provides a writable $HOME, and entrypoint.sh creates .claude.json from
CLAUDE_CODE_API_KEY_HELPER env var.

Fixes github/gh-aw#16214

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 16, 2026 23:24
@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

📰 DEVELOPING STORY: Smoke Copilot reports was cancelled. Our correspondents are investigating the incident...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

Chroot tests failed Smoke Chroot was cancelled - See logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

💫 TO BE CONTINUED... Smoke Claude was cancelled! Our hero faces unexpected challenges...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

🌑 The shadows whisper... Smoke Codex was cancelled. The oracle requires further meditation...

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 82.71% 82.84% 📈 +0.13%
Statements 82.63% 82.76% 📈 +0.13%
Functions 82.74% 82.74% ➡️ +0.00%
Branches 74.78% 74.88% 📈 +0.10%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 84.1% → 84.7% (+0.51%) 83.4% → 83.8% (+0.49%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copy link
Collaborator Author

@Mossaka Mossaka left a comment

Choose a reason for hiding this comment

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

Code Review

Summary

This PR correctly removes the .claude.json file bind mount that was causing Claude Code to fail on atomic writes (rename() returns EBUSY on Linux file bind mounts). The fix is clean and minimal.

Analysis

Correctness:

  • The writable home volume (${workDir}-chroot-home:/host${effectiveHome}:rw) at docker-manager.ts:525 already provides a writable $HOME directory. Files created within it (including .claude.json) support normal filesystem operations including atomic writes.
  • entrypoint.sh lines 126-163 correctly handle all three cases: file exists with matching apiKeyHelper, file exists without apiKeyHelper, and file doesn't exist. The "file doesn't exist" path (lines 157-161) is the one that will now be taken since docker-manager no longer pre-creates the file.
  • No side effects — the bind mount was redundant with the writable home volume, and the entrypoint already had the creation logic.

Comment quality:

  • The replacement comment (lines 552-555) is clear and well-written. It documents both the what (no bind mount) and the why (atomic writes), plus points to the alternative mechanism.

One minor nit (non-blocking):

  • entrypoint.sh line 150 still says "overwrites empty {} created by docker-manager". Since docker-manager no longer creates this file, the comment is now stale. This is a cosmetic issue and doesn't affect correctness — the code path still works fine if a file happens to exist without apiKeyHelper for any other reason.

Verdict

The change is correct, well-documented, and addresses the root cause. The entrypoint handles all edge cases properly. LGTM.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Mossaka
Copy link
Collaborator Author

Mossaka commented Feb 16, 2026

Verification Report: PR #911 — Remove .claude.json file bind mount

Issue Summary (#16214)

AWF v0.18.0 introduced a regression where ~/.claude.json was bind-mounted as an individual file into the agent container. Claude Code performs atomic writes (write to temp file, then rename()), but Linux file bind mounts return EBUSY on rename(). The fallback direct write also failed with EACCES. Claude Code exited silently with 0 tool calls.

Fix Analysis

The change: Removes the file bind mount of ~/.claude.json (lines 549-568 in docker-manager.ts), replacing it with a comment explaining why file bind mounts must not be used.

Root cause addressed: YES. The file bind mount (${claudeJsonPath}:/host${claudeJsonPath}:rw) is the direct cause of EBUSY on rename(). After removal, .claude.json lives on the writable home volume (${emptyHomeDir}:/host${effectiveHome}:rw at line 525), which is a regular Docker volume that supports rename().

Verification Checklist

1. Atomic writes will work after the fix

  • The writable home volume (docker-manager.ts:524-525) provides /host$HOME as a regular writable directory
  • rename() works correctly on regular Docker volumes (no EBUSY)
  • Claude Code can write temp file + rename atomically

2. entrypoint.sh still creates .claude.json correctly

  • entrypoint.sh:126-162 is unchanged by this PR
  • When CLAUDE_CODE_API_KEY_HELPER env var is set:
    • Chroot mode writes to /host$HOME/.claude.json (line 131)
    • Creates file if it doesn't exist (lines 157-161)
    • Writes apiKeyHelper if file exists but missing it (lines 150-154)
    • Validates existing apiKeyHelper matches env var (lines 138-148)
  • The file is created on the writable home volume, not a bind mount

3. API proxy is unaffected

  • docker-manager.ts:1015-1028: CLAUDE_CODE_API_KEY_HELPER env var is set when config.anthropicApiKey is provided — independent of bind mount
  • API proxy sidecar (awf-api-proxy, lines 950-1032) is a separate container with its own volumes — no dependency on .claude.json mount
  • Flow: docker-manager.ts sets CLAUDE_CODE_API_KEY_HELPER='/usr/local/bin/get-claude-key.sh' → env var passed to agent → entrypoint.sh reads it → creates .claude.json on writable volume → Claude Code reads it

4. One-shot token handling is unaffected

  • One-shot token library (entrypoint.sh:254-272) is copied to /host/tmp/awf-lib/ and loaded via LD_PRELOADindependent of .claude.json
  • Token unsetting from /proc/1/environ (entrypoint.sh:193-224) — independent of .claude.json

5. No other code references the removed bind mount

  • The removed code created ~/.claude.json on the host if it didn't exist, then mounted it. No other code depends on this host-side file creation.
  • The CLAUDE_CODE_API_KEY_HELPER env var flow is the sole mechanism for .claude.json content — it works via entrypoint.sh, not the bind mount.

Conclusion

The fix correctly addresses issue #16214. The file bind mount was the root cause of EBUSY/EACCES errors. After removal, .claude.json lives on the writable home volume where atomic writes work correctly. The API proxy, one-shot token handling, and entrypoint.sh .claude.json creation are all unaffected.

This aligns with the project's documented guidance in CLAUDE.md:

Do not bind-mount individual files into the agent container. Linux file bind mounts prevent atomic writes (temp file + rename() returns EBUSY). Tools like Claude Code rely on atomic writes for config files. Instead, let the writable home volume provide the file, and use entrypoint.sh to seed initial content.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Removes the host file bind-mount for ~/.claude.json in generateDockerCompose to avoid Linux file bind-mount semantics that can break Claude Code’s atomic config writes, relying instead on the existing writable home volume and entrypoint initialization.

Changes:

  • Deleted host-side creation of ~/.claude.json and its corresponding file bind-mount into /host.
  • Added an inline note documenting why ~/.claude.json must not be mounted as a file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +554 to +555
// The writable home volume provides a writable $HOME, and entrypoint.sh
// creates ~/.claude.json with apiKeyHelper content from CLAUDE_CODE_API_KEY_HELPER.
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The comment implies entrypoint.sh always creates ~/.claude.json, but that only happens when CLAUDE_CODE_API_KEY_HELPER is set. Consider rewording to make that conditional explicit (and/or mention that otherwise Claude Code will create/manage the file itself on the writable home volume).

Suggested change
// The writable home volume provides a writable $HOME, and entrypoint.sh
// creates ~/.claude.json with apiKeyHelper content from CLAUDE_CODE_API_KEY_HELPER.
// The writable home volume provides a writable $HOME. When CLAUDE_CODE_API_KEY_HELPER
// is set, entrypoint.sh creates ~/.claude.json with apiKeyHelper content; otherwise
// Claude Code itself will create and manage ~/.claude.json on the writable home volume.

Copilot uses AI. Check for mistakes.
Comment on lines +552 to +555
// NOTE: ~/.claude.json is NOT bind-mounted as a file. File bind mounts on Linux
// prevent atomic writes (temp file + rename), which Claude Code requires.
// The writable home volume provides a writable $HOME, and entrypoint.sh
// creates ~/.claude.json with apiKeyHelper content from CLAUDE_CODE_API_KEY_HELPER.
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

This change removes a previously mounted volume, but there isn’t a unit test asserting that ~/.claude.json is not present as a file bind-mount in the generated agent volumes. Since docker-manager.test.ts already asserts other chroot volume mounts, add a regression test to ensure the volumes list does not contain an entry ending in ':/host$HOME/.claude.json:rw' (or the equivalent resolved path).

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Contributor

github-actions bot commented Feb 16, 2026

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

@github-actions
Copy link
Contributor

Deno Build Test Results

Project Tests Status
oak 1/1 ✅ PASS
std 1/1 ✅ PASS

Overall: ✅ PASS

All Deno tests completed successfully.

AI generated by Build Test Deno

@github-actions
Copy link
Contributor

C++ Build Test Results

Project CMake Build Status
fmt PASS
json PASS

Overall: PASS

All C++ projects built successfully.

AI generated by Build Test C++

@github-actions
Copy link
Contributor

Smoke Test Results (Copilot)

Last 2 Merged PRs:

Test Results:

Status: PASS

cc @Mossaka

AI generated by Smoke Copilot

@github-actions
Copy link
Contributor

Go Build Test Results

Project Download Tests Status
color 1/1 PASS
env 1/1 PASS
uuid 1/1 PASS

Overall: PASS

All Go projects successfully downloaded dependencies and passed tests.

AI generated by Build Test Go

@github-actions
Copy link
Contributor

Node.js Build Test Results ✅

All Node.js projects tested successfully!

Project Install Tests Status
clsx All passed PASS ✅
execa All passed PASS ✅
p-limit All passed PASS ✅

Overall: PASS ✅

All npm install and test commands completed successfully without errors.

AI generated by Build Test Node.js

@github-actions
Copy link
Contributor

.NET Build Test Results

Project Restore Build Run Status
hello-world PASS
json-parse PASS

Overall: PASS

All .NET projects successfully restored, built, and executed.

AI generated by Build Test .NET

@github-actions
Copy link
Contributor

Smoke Test Results - Claude Engine

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Successfully retrieved PR data
  • ✅ Playwright: Navigated to https://github.com, title verified
  • ✅ File Writing: Created /tmp/gh-aw/agent/smoke-test-claude-22080511826.txt
  • ✅ Bash: File verification successful

Status: PASS

AI generated by Smoke Claude

@github-actions
Copy link
Contributor

Build Test: Bun ✅

Project Install Tests Status
elysia 1/1 PASS
hono 1/1 PASS

Overall: PASS

All Bun build tests completed successfully.

AI generated by Build Test Bun

@github-actions
Copy link
Contributor

Rust Build Test Results ✅

Project Build Tests Status
fd 1/1 PASS
zoxide 1/1 PASS

Overall: PASS

All Rust projects built and tested successfully.

AI generated by Build Test Rust

@github-actions
Copy link
Contributor

Merged PRs: perf: parallelize container image builds in release workflow | feat: add ARM64 multi-architecture container builds
Tests: GitHub MCP ✅ | safeinputs-gh ✅ | Playwright ✅ | Tavily MCP ❌
Tests: file write ✅ | bash cat ✅ | discussion comment ✅ | build ✅
Overall: FAIL

AI generated by Smoke Codex

@github-actions
Copy link
Contributor

Chroot Version Comparison Test Results

Runtime Host Version Chroot Version Match?
Python 3.12.12 3.12.3 ❌ NO
Node.js v24.13.0 v20.20.0 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall Status: ❌ Tests Failed

The chroot environment does not have matching versions for Python and Node.js. Go versions match correctly.

AI generated by Smoke Chroot

@Mossaka Mossaka merged commit 551cc38 into main Feb 17, 2026
92 checks passed
@Mossaka Mossaka deleted the fix/claude-json-bind-mount branch February 17, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AWF v0.18.0 chroot makes /home/runner/.claude.json unwritable, Claude Code exits silently

1 participant