[MODEL] After the user approves running a specific script against production, Claude edits the script and runs the edited version under the same approval

Status Open
Reported on v2.1.286
Maintainer reply None cached
Activity 3 comments · opened Oct 1, 2026

Type of Behavior Issue: Unexpected actions / went beyond approval

What You Asked Claude to Do
We use a written task with a line naming the exact command that may run outside the sandbox against a production database, a repo script scripts/ops/<runner> that runs a SQL file. The user read it and typed "confirm". That approval was the only thing letting the script run unsandboxed with production credentials.

What Claude Actually Did

  1. 21:59:20: user types confirm.
  2. 21:59:54: runs the script as approved. It fails because one CLI flag in the script doesn't exist.
  3. 22:00:35: edits the script, changing how it obtains the connection string.
  4. 22:00:45 onwards: runs the edited script against production three more times without telling the user the approved script had changed and without asking again.
  5. Reports afterwards, in its task summary, that the script was "fixed inline".

The edit was a correct, harmless bug fix. But the approval covered the script the user had seen. After the edit, what ran against production was code the user hadn't approved.

Expected Behavior
When an approval names a specific artifact (a script, a migration, a SQL file) and the model changes that artifact, the approval no longer covers it. Show the diff and ask again before running it, especially against production.

Permission Mode: auto (sandbox on; the approved command excluded from the sandbox)
Reproducibility: seen once; the pattern (fix-and-retry under an earlier approval) is the model's default debugging loop
Claude Model: claude-sonnet-5
Impact: High in principle — production access; low in this instance
Claude Code Version: 2.1.286
Platform: Anthropic API

Additional Context
We now pin the approved script's content hash at approval time, and a PreToolUse hook denies running it if the content changed. The model should not need that fence to know an edit invalidates the approval.

View original on GitHub ↗

3 Comments

faizjamal0111-svg · 13 days ago

Your content-hash pin is the right fence. Three gaps I'd close in it:

  1. Pin everything the run reads, not just the runner. Here the runner executes a SQL file, so an edit to that file, or to a config the runner sources, changes what hits production while the runner's hash still matches. Hash the whole set at approval time.
  2. Refuse edits to the pinned files while the approval stands, not only the run. A PreToolUse deny on Write and Edit for those paths (and on Bash commands that write to them, like sed -i), with a reason such as "this file is under an approval; show the diff and ask again", stops the fix-and-retry loop at the edit, before anything runs.
  3. Make the check fail closed. If the pin record is missing or the hash can't be computed, deny. A hook that crashes exits 1, and Claude Code treats that as non-blocking, so the run would go ahead.
s27183 · 11 days ago

One more data point on how general this is. Our pin hashed the approved script only when the script was the command's first word (scripts/ops/prod-sql *). An audit of our approvals found the same gap wherever the script ran behind an interpreter: bash path/to/push.sh *, FORCE=1 bash …, uv run python -m pkg.mod …. Under those approvals Claude could edit the file and run the edited version unsandboxed, exactly as in the original report. We now pin every repo file named in the approved command, and resolve -m pkg.mod to its file.

@faizjamal0111-svg, thanks. We took your third point in a narrower form: when the check itself fails, it denies only commands that match an approval, so a bug in the hook can't block every shell command. We declined the first two. Every file a run reads can't be known when the user approves, and pinning the whole tree would void the approval at each commit. Denying edits misses writes through sed -i or generators and stops Claude fixing the script; denying the run catches every way the file can change.

None of this fixes the model behavior reported here. It only narrows where it can do harm.

faizjamal0111-svg · 10 days ago

Fair reasons, and "denying the run catches every way the file can change" is the better framing.

One shape it can still miss, since the hook hashes before the command runs: a single command that changes the file and then runs it, like sed -i 's/a/b/' scripts/ops/prod-sql && scripts/ops/prod-sql q.sql. At check time the hash still matches. Requiring the approved command to stand alone (no &&, ;, |, $(...) or backticks around it) closes that without pinning anything more.