Skip to content

[3374 by Claude Opus 5.5] 0.9.0 release logic fix: on launcher-managed installs, Claude agent containers run without ~/.claude.json, and after the first Claude run later runs fail with 'Claude configuration file not found at: /home/node/.claude.json / A backup file exists at:... - #3375

Merged
integry merged 1 commit into
mainfrom
3374/claude-opus-5-5-0-9-0-release-logic-fix-o-20261029-0906-3ub
Oct 29, 2026

Conversation

@propr-dev

@propr-dev propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #3374

Branch: 3374/claude-opus-5-5-0-9-0-release-logic-fix-o-20261029-0906-3ub
Commits: ✅ Changes committed (f7ef8e9)


AI Processing Completed

Execution Details:

Summary:
I've fixed the issue. Upstream main was the same commit as this branch, so nothing there already covered it.

What the Claude CLI actually does (checked against the installed 2.1.293):

  • It replaces .claude.json with a temp file plus rename, so a shared read-write bind mount of the host file isn't safe.
  • Before each save it copies the config into ~/.claude/backups and keeps only the 5 newest. Agent containers share that directory, so they could delete the host login's own backups, not just add stub files.
  • I reproduced the problem with the real CLI. A HOME that has a backup but no .claude.json prints the exact "configuration file not found / A backup file exists" error. With a seeded {} config it gets past that and stops only at "Not logged in", which is expected because the sandbox has no credentials. The run also wrote an 84-byte backup stub, the same as on your install.

Changes

  1. Launcher (docker/launcher/orchestrator.mjs): agentCredentialArgs now mounts the host .claude.json read-only into the worker and api at the same host path, and sets CLAUDE_JSON_PATH. The file is the one next to HOST_CLAUDE_DIR, or HOST_CLAUDE_JSON if set.
    • The derived file is mounted only if it exists. HOST_CLAUDE_JSON is always mounted, because a launcher running in a container can't see host files.
    • The mount uses --mount, so a missing file makes the run fail instead of Docker creating a directory at the config's path.
    • validateEnv checks the override.
  2. Agent containers: a new packages/core/src/claude/docker/claudeConfigSeed.ts is used by both Claude docker-args builders (dockerArgsBuilder.ts and the older claudeHelpers.ts).
    • The seed source is CLAUDE_JSON_PATH, falling back to os.homedir() as before. It is mounted read-only outside HOME.
    • Before the entrypoint runs, the wrapper (both the repo-setup and workflow variants, as root) copies it into a private ~/.claude.json. If there's no host file it writes {}. It never overwrites an existing config and never writes back to the host.
    • ~/.claude/backups is now a tmpfs private to each container, so host backups are never read, added to or pruned. propr check --validate-agents image runs get the same tmpfs.
  3. propr check: a new "Agent config: claude (.claude.json)" row reports one of three states: mounted (OK), missing (warning, with a hint to run claude or set HOST_CLAUDE_JSON), or a bad override (fail).
  4. Tests:
    • test/orchestratorClaudeConfig.test.mjs: launcher args with the file, without it, with the override, and with commas in the path.
    • test/claudeConfigSeed.test.ts: path selection, docker args, and the seed script run in bash, including the "backup exists, config missing" regression with the backups left untouched.
    • A checkCommands test for the new row.
  5. Docs: a 0.9.0 Fixed changelog bullet; HOST_CLAUDE_JSON added to the configuration reference, the launcher examples and the settings catalog, and the settings docs regenerated.

Validation: all of these pass:

  • orchestrator tests (147)
  • the Claude, docker-args, wrapper and workflow suites (251, plus 69 with 6 skipped)
  • npm run test:unit (747)
  • npm run lint --workspaces --if-present -- --max-warnings 0
  • root lint and typecheck, CLI and core typecheck
  • release:verify for v0.9.0

No test ran inside a real Docker agent container or a real propr start install, and Claude only ran on its own here.

Behaviour changes to know about:

  • Dev and compose setups also stop sharing a read-write ~/.claude.json. Changes an agent makes to it are now thrown away when the container exits.
  • If the host user deletes ~/.claude.json after ProPR starts, Claude runs fail on the mount until ProPR is restarted.
  • If ~/.claude/backups doesn't exist on the host, Docker creates it, owned by root, as the tmpfs mount point.

There are no visual previews, since the only user-facing change is a text row in propr check.

Detailed Logs:

  • Conversation: 247 messages
  • Session: a9dcccfb-45e9-4264-96b2-3aaa9802d1d2

Log files stored at:

  • conversation: /usr/src/app/logs/executions/issue-3374-2026-10-29T09-24-54-078Z-conversation.json
  • output: /usr/src/app/logs/executions/issue-3374-2026-10-29T09-24-54-078Z-output.txt
Latest Conversation Messages
ASSISTANT: [content unavailable]

ASSISTANT: I've fixed the issue. Upstream `main` was the same commit as this branch, so nothing there already covered it.

**What the Claude CLI actually does (checked against the installed 2.1.293):**
- It repl...


This PR was created automatically by ProPR after processing issue #3374.


💡 Need changes?

Comment on this PR to request refinements — the AI agent monitors comments and will update the implementation based on your feedback. Keep iterating until you're satisfied!

…nch, so nothing there already covered it.

What the Claude CLI actually does (checked against the installed 2.1.293):
- It replaces `.claude.json` with a temp file plus rename, so a shared read-write bind mount of the host file isn't safe.
- Before each save it copies the config into `~/.claude/backups` and keeps only the 5 newest. Agent containers share that directory, so they could delete the host login's own backups, not just add stub files.
- I reproduced the problem with the real CLI. A HOME that has a backup but no `.claude.json` prints the exact "configuration file not found / A backup file exists" error. With a seeded `{}` config it gets past that and stops only at "Not logged in", which is expected because the sandbox has no credentials. The run also wrote an 84-byte backup stub, the same as on your install.

Changes
1. Launcher (`docker/launcher/orchestrator.mjs`): `agentCredentialArgs` now mounts the host `.claude.json` read-only into the worker and api at the same host path, and sets `CLAUDE_JSON_PATH`. The file is the one next to `HOST_CLAUDE_DIR`, or `HOST_CLAUDE_JSON` if set.
   - The derived file is mounted only if it exists. `HOST_CLAUDE_JSON` is always mounted, because a launcher running in a container can't see host files.
   - The mount uses `--mount`, so a missing file makes the run fail instead of Docker creating a directory at the config's path.
   - `validateEnv` checks the override.
2. Agent containers: a new `packages/core/src/claude/docker/claudeConfigSeed.ts` is used by both Claude docker-args builders (`dockerArgsBuilder.ts` and the older `claudeHelpers.ts`).
   - The seed source is `CLAUDE_JSON_PATH`, falling back to `os.homedir()` as before. It is mounted read-only outside HOME.
   - Before the entrypoint runs, the wrapper (both the repo-setup and workflow variants, as root) copies it into a private `~/.claude.json`. If there's no host file it writes `{}`. It never overwrites an existing config and never writes back to the host.
   - `~/.claude/backups` is now a tmpfs private to each container, so host backups are never read, added to or pruned. `propr check --validate-agents` image runs get the same tmpfs.
3. `propr check`: a new "Agent config: claude (.claude.json)" row reports one of three states: mounted (OK), missing (warning, with a hint to run `claude` or set `HOST_CLAUDE_JSON`), or a bad override (fail).
4. Tests:
   - `test/orchestratorClaudeConfig.test.mjs`: launcher args with the file, without it, with the override, and with commas in the path.
   - `test/claudeConfigSeed.test.ts`: path selection, docker args, and the seed script run in bash, including the "backup exists, config missing" regression with the backups left untouched.
   - A `checkCommands` test for the new row.
5. Docs: a 0.9.0 Fixed changelog bullet; `HOST_CLAUDE_JSON` added to the configuration reference, the launcher examples and the settings catalog, and the settings docs regenerated.

Validation: all of these pass:
- orchestrator tests (147)
- the Claude, docker-args, wrapper and workflow suites (251, plus 69 with 6 skipped)
- `npm run test:unit` (747)
- `npm run lint --workspaces --if-present -- --max-warnings 0`
- root lint and typecheck, CLI and core typecheck
- `release:verify` for v0.9.0

No test ran inside a real Docker agent container or a real `propr start` install, and Claude only ran on its own here.

Behaviour changes to know about:
- Dev and compose setups also stop sharing a read-write `~/.claude.json`. Changes an agent makes to it are now thrown away when the container exits.
- If the host user deletes `~/.claude.json` after ProPR starts, Claude runs fail on the mount until ProPR is restarted.
- If `~/.claude/backups` doesn't exist on the host, Docker creates it, owned by root, as the tmpfs mount point.

There are no visual previews, since the only user-facing change is a text row in `propr check`.
@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

🔍 AI Code Review — codex:gpt-6-astra

Overall Evaluation

The PR addresses the reported failure by seeding a private writable Claude config and isolating each container’s backups. Ready to merge within the visible scope, conditional on the pending Validate Changes check.

✅ Private configuration — Both Claude Docker argument builders use the shared seed helper, and both wrapper implementations initialize the config before entrypoint execution.

✅ Backup isolation — Per-container tmpfs mounts prevent agent runs from pruning the host’s backup directory.

✅ Focused coverage — Added tests cover source selection, launcher mounts, missing-config fallback, and preservation of existing configurations.

The supplied issue description is truncated; this review covers its visible requirements and the complete supplied diff. No commands were run.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

S1: 🟢 Exercise real container lifecycle

The new tests verify generated arguments and execute the seed script, but do not exercise Docker’s nested tmpfs mount and subsequent privilege drop. An integration test running two successive containers against a directory containing host backups would strengthen regression coverage. This is optional; the supplied code does not demonstrate a failure in that path.

S2: 🟢 Clarify optional launcher configuration

The launcher examples now unconditionally set HOST_CLAUDE_JSON, while an explicit missing file deliberately prevents service startup. Annotating this line as applicable only when the host file exists would help users following the examples with another provider or without a host Claude login. The configuration reference already describes the underlying behavior correctly.

Score

The implementation is focused and internally consistent, with relevant regression coverage and no verified code blockers. Current checks report 38 passes, no failures, and one pending check; actual container execution was not independently verified during this review.

Score: 9/10


🤖 Review Details

  • Model: GPT-6 Astra
  • Time: 1m 9s
  • Tokens: 59,514 (58,639 in / 875 out)
  • Cost: $0.52

View Task


💡 Next step: Comment /fix to address every F# merge blocker, /fix all for every pending blocker and suggestion, or name records explicitly, as in /fix F3 S5.
F# and S# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. S# suggestions stay optional: they are implemented only when you name them or request /fix all, and they never relax a merge blocker.

Copy and edit to choose records from this review:

/fix S1 S2

🤖 Review by ProPR

@integry
integry merged commit 3391a8c into main Oct 29, 2026
61 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant