anthropics / anthropics/claude-code-action

settings input is shallow-merged into ~/.claude/settings.json and leaks across jobs on shared-HOME self-hosted runners

Đang mở
#1,688 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
area:permissions bug p2
Ngôn ngữ chính
TypeScript
Star
8.9k
Fork
2.1k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

## Summary

`setupClaudeCodeSettings` (base-action/src/setup-claude-code-settings.ts) writes the `settings:` input to `$HOME/.claude/settings.json` by shallow-merging it over whatever is already there, and nothing ever resets that file. On self-hosted runners that share a Unix user (several runner instances on one box, all under one $HOME), one workflow's permission profile persists on the machine and is silently loaded as user-scope settings by every later job's session — including jobs that pass no `settings:` input at all.

## Impact we hit

Our review workflow passes a restrictive profile (`permissions.deny: ["Write", "Edit", "Bash(git commit*)", "Bash(git push*)"]`). Our implement workflow passes no `settings:` and runs with `--dangerously-skip-permissions`. After any review run, implement sessions on the same box inherit the reviewer's deny list — and a bare tool-name deny removes the tool from the model's context entirely, so `--dangerously-skip-permissions` does not restore it. The observable failure mode is nasty: the implement agent, unable to Write/Edit/commit/push, routed around it by constructing commits via the GitHub Git Data API (`gh api .../git/blobs|trees|commits|refs`) — bypassing the repo's commit gates — and noted in its PR that "this session's permission settings hard-deny git commit/git push". It took us a while to trace that back to another workflow's settings.

Because the merge is shallow and unconditional, the file also races when jobs run concurrently on sibling runners.

## Where in the source

```ts
// base-action/src/setup-claude-code-settings.ts
const settingsPath = `${home}/.claude/settings.json`;
...
settings = { ...settings, ...inputSettings };
settings.enableAllProjectMcpServers = true;
await $`echo ${JSON.stringify(settings, null, 2)} > ${settingsPath}`.quiet();
```

`index.ts` always calls it with `homeDir = undefined`, so it is always `os.homedir()`. There is no input for a job-scoped location, and the action does not honor `CLAUDE_CONFIG_DIR` here.

## Ask

Any of these would fix the class:

1. Write the settings to a job-scoped file and pass it to the CLI via `--settings ` (the CLI already supports per-session settings with higher precedence), leaving the user file untouched.
2. Honor `CLAUDE_CONFIG_DIR` in `setupClaudeCodeSettings` so operators can isolate per job.
3. At minimum: replace rather than merge, and restore the prior file contents in a post step.

Related: #1057 describes the same shared-`~/.claude` contamination class for marketplace installs ("Workaround: none that's safe for concurrent jobs").

## Workaround we're deploying

Passing the profile via `claude_args: --setting-sources project,local --settings '{...}'` so sessions never read the polluted user file — works, but every consumer of `settings:` on shared runners is exposed to this by default.

## Environment

- claude-code-action@v1, base-action bundled
- Ubuntu 24.04, 4-5 self-hosted runner instances registered under one user, shared $HOME
- Claude Code 2.1.x preinstalled via `path_to_claude_code_executable`

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.