You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
restoreConfigFromBase is a correct security control and I'm not asking to weaken it. But for a repository whose source tree lives under .claude/, the review agent silently reads base-branch content for most of the PR, and the .claude-pr/ snapshot that exists to prevent this is not discoverable through the tools the agent is most likely to use.
The result is a false negative: a stale read returns the already-merged version, which looks correct, so the reviewer reports no finding and leaves no trace. Unlike #1543 (false positives on the reversal), nothing in the output indicates anything went wrong.
The concrete defect: .claude-pr/ is invisible to Glob
The snapshot mirrors real paths, so its top level contains dot-paths. A glob's * does not match a leading dot:
probe
result
ls -a .claude-pr
✅ .claude, CLAUDE.md — complete
Glob(".claude-pr/*")
❌ CLAUDE.md only — silently omits .claude/
Glob(".claude-pr/.*")
❌ no files found
Glob(".claude-pr/**")
⚠️ 100+ files, truncated
So an agent that enumerates the snapshot with Glob concludes only CLAUDE.md was reverted and reads everything else from the working tree — with a positive, self-generated licence to do so.
Compounding it: .claude-pr/ is normally gitignored (per #1275/#1277, to stop git add -A committing it), and ripgrep honours .gitignore. So it also drops out of every unscoped Grep. A search for a symbol the PR adds returns zero hits with no indication a second tree was skipped — and re-running the search returns the same empty answer.
Observed impact
In our repo the engine and its entire test suite live under .claude/ (144 of 177 tracked files; 100% of the Python source). Confirmed on v1.0.89:
On one PR, a review subagent Read a modified test file from the working tree at two offsets (offset=87, offset=401). The file was +26/-3; both reads returned base content at base line numbers, in the region the PR changed.
Corroboration from the same job log: the action's own file listing reported (MODIFIED) +3/-3 SHA: b9bd409…, where b9bd409 is the old side of index b9bd409..fafa74e — i.e. it hashed the reverted working tree, so even the reported diffstat was wrong.
Reviews were only mostly fine because gh pr diff and git show HEAD:<path> happen to be authoritative. Whether any given review is sound depends on which tool the model reached for.
Also worth noting: gh pr diffcannot substitute for the snapshot here. Those two reads were context reads around the hunks, and a changeset can't serve them — which is exactly why the full-tree snapshot is the right primitive, if only it were reachable.
Suggested directions (non-prescriptive)
Roughly in increasing cost:
Tell the agent the snapshot exists. The action already knows the exact reverted set at runtime; injecting one line into the prompt (these paths were reverted; read them at .claude-pr/<path>) would close most of this. Today the only signal is a console.log in the job log, which the agent does not read.
Make the snapshot enumerable — e.g. also emit a plain manifest (.claude-pr/RESTORED.txt) so discovery does not depend on dot-glob semantics.
A scoped opt-out — e.g. a restore_config_paths input, so a repo can narrow the set to the files actually read at CLI startup rather than all of .claude/. Currently SENSITIVE_PATHS is hardcoded with no input (still true at v1.0.185), and it has grown from 2 paths (Restore .claude/ and .mcp.json from PR base branch before CLI runs #1066) to 8, so the blast radius only widens over time.
We've mitigated locally by instructing our reviewer to ls -a .claude-pr first and never to read a reverted path from the working tree. That works, but it depends on the model following prose — it is not a structural fix, and every consumer with a .claude/-hosted codebase has to rediscover the problem first, which in our case took several review rounds and one confirmed missed finding.
Environment
anthropics/claude-code-action@v1.0.89 (pinned; v1.0.90 broke Bedrock SigV4 auth for us)
Summary
restoreConfigFromBaseis a correct security control and I'm not asking to weaken it. But for a repository whose source tree lives under.claude/, the review agent silently reads base-branch content for most of the PR, and the.claude-pr/snapshot that exists to prevent this is not discoverable through the tools the agent is most likely to use.The result is a false negative: a stale read returns the already-merged version, which looks correct, so the reviewer reports no finding and leaves no trace. Unlike #1543 (false positives on the reversal), nothing in the output indicates anything went wrong.
Distinct from existing issues
.claude/changes being invisible. The.claude-pr/snapshot addressed availability. This issue is that the snapshot is unreachable in practice, which is a separate, still-open half.restoreConfigFromBaseshould cover nestedCLAUDE.mdfiles #1270 — nestedCLAUDE.mdcoverage. Orthogonal..huskyspecifically. Orthogonal.The concrete defect:
.claude-pr/is invisible toGlobThe snapshot mirrors real paths, so its top level contains dot-paths. A glob's
*does not match a leading dot:ls -a .claude-pr.claude,CLAUDE.md— completeGlob(".claude-pr/*")CLAUDE.mdonly — silently omits.claude/Glob(".claude-pr/.*")Glob(".claude-pr/**")So an agent that enumerates the snapshot with
Globconcludes onlyCLAUDE.mdwas reverted and reads everything else from the working tree — with a positive, self-generated licence to do so.Compounding it:
.claude-pr/is normally gitignored (per #1275/#1277, to stopgit add -Acommitting it), and ripgrep honours.gitignore. So it also drops out of every unscopedGrep. A search for a symbol the PR adds returns zero hits with no indication a second tree was skipped — and re-running the search returns the same empty answer.Observed impact
In our repo the engine and its entire test suite live under
.claude/(144 of 177 tracked files; 100% of the Python source). Confirmed onv1.0.89:Reada modified test file from the working tree at two offsets (offset=87,offset=401). The file was+26/-3; both reads returned base content at base line numbers, in the region the PR changed.(MODIFIED) +3/-3 SHA: b9bd409…, whereb9bd409is the old side ofindex b9bd409..fafa74e— i.e. it hashed the reverted working tree, so even the reported diffstat was wrong.gh pr diffandgit show HEAD:<path>happen to be authoritative. Whether any given review is sound depends on which tool the model reached for.Also worth noting:
gh pr diffcannot substitute for the snapshot here. Those two reads were context reads around the hunks, and a changeset can't serve them — which is exactly why the full-tree snapshot is the right primitive, if only it were reachable.Suggested directions (non-prescriptive)
Roughly in increasing cost:
these paths were reverted; read them at .claude-pr/<path>) would close most of this. Today the only signal is aconsole.login the job log, which the agent does not read..claude-pr/RESTORED.txt) so discovery does not depend on dot-glob semantics.restore_config_pathsinput, so a repo can narrow the set to the files actually read at CLI startup rather than all of.claude/. CurrentlySENSITIVE_PATHSis hardcoded with no input (still true atv1.0.185), and it has grown from 2 paths (Restore .claude/ and .mcp.json from PR base branch before CLI runs #1066) to 8, so the blast radius only widens over time.We've mitigated locally by instructing our reviewer to
ls -a .claude-prfirst and never to read a reverted path from the working tree. That works, but it depends on the model following prose — it is not a structural fix, and every consumer with a.claude/-hosted codebase has to rediscover the problem first, which in our case took several review rounds and one confirmed missed finding.Environment
anthropics/claude-code-action@v1.0.89(pinned;v1.0.90broke Bedrock SigV4 auth for us)global.anthropic.claude-opus-5v1.0.74(9ddce40, Restore .claude/ and .mcp.json from PR base branch before CLI runs #1066);.claude-pr/snapshot added inv1.0.89(5150ea9, fix: snapshot PR's .claude/ to .claude-pr/ before security restore #1172)