rm-on-variable-path confirmation dialog fires under bypassPermissions with no way to disable/detect it, freezing unattended background agents for hours

Status Open
Maintainer reply None cached
Activity 4 comments · opened Sep 10, 2026

Summary

The "Dangerous rm operation on possibly-empty variable path" confirmation dialog fires even under bypassPermissions mode, and cannot be suppressed by any permissions.allow rule or PreToolUse hook returning allow. In an unattended multi-agent setup (background teammate sessions spawned via the Agent/Task tool, running with no one watching the terminal), this silently freezes the teammate's pane until a human happens to notice and click through it.

Impact

Running Claude Code with background/teammate agents (the documented Agent-team pattern), a teammate that emits any rm -r/rm -rf command touching a shell-variable path stalls indefinitely — not for seconds, but until a human physically clicks "yes" in that pane's terminal. Measured on our end: one teammate sat frozen for ~13 hours, another for ~8 hours more shortly after, both fully idle, silently waiting on a dialog nobody knew existed until we happened to check the pane directly.

This is a severe usability regression for any workflow that relies on bypassPermissions specifically because it promises unattended execution. If a check is going to override bypassPermissions, it needs a way to be silenced (e.g., an explicit opt-out flag/env var), or at minimum it needs to be observable from outside the frozen pane (surfaced to the spawning session, or logged somewhere pollable) so an orchestrating process can detect and handle it instead of a human being the only possible unblock path.

Reproduction

Any of the following patterns raise the confirmation, even in bypassPermissions mode:

  • rm -rf "$T" (quoted variable path)
  • rm -rf "$TMP/x"
  • trap 'rm -rf "$TMP"' EXIT
  • Confirmed to also fire on non-rm content that merely contains the literal text rm -r near a $var-shaped path — e.g. a grep pattern string like 'rm -rf\|rm -r ' followed by a "$D/$s.jsonl"-style path in the same Bash call raised the same dialog, with no rm actually being executed.

What we've verified

  • Per Claude Code's own docs (https://code.claude.com/docs/en/permission-modes.md, sections "Skip all checks with bypassPermissions mode" and "Critical paths"): rm/rmdir removals targeting a "critical path" still prompt in bypassPermissions, and no permissions.allow rule or PreToolUse hook returning allow can approve it.
  • No environment variable, CLI flag, settings key, or hook we could find disables this specific check.
  • The check appears to be a textual/pattern scan on the submitted command text, not a semantic check of what the command actually does — see the grep-pattern false-positive above.

Ask

Either:

  1. An explicit, documented opt-out (env var or settings flag) for this specific check, scoped narrowly enough that it doesn't reopen the broader "critical path" protections bypassPermissions already carves out exceptions for, or
  2. A way for an orchestrating/parent session to be notified when a spawned teammate is blocked on this dialog, so it can be handled programmatically instead of requiring a human to physically be at the keyboard.

Happy to provide more reproduction detail if useful.

View original on GitHub ↗

3 Comments

Benjamin-Sterrett · 1 month ago

Update: confirmed a second, broader trigger — NOT just rm -r

A teammate session just diagnosed a ~35.5-hour freeze (submitted, sat blocked, only ran after a human physically clicked through) on a Bash call that contained no rm -r/rm -rf at all — it had been deliberately avoiding that exact pattern the whole run per our own internal mitigation doc.

The blocked call was a cat > file <<'HEREDOC' writing a ~241-line script followed by bash -n on it. Diffing the shell content against three earlier, structurally similar calls from the same session that ran unblocked, the only shapes unique to the blocked one were:

  • cp -R "$SOME_VAR" "$OTHER_VAR" — a recursive copy with two shell-variable operands
  • mkdir -p "$HOME/.some/path" — a write under $HOME

Non-recursive rm -f "$VAR"/* lines were present in both the blocked call and the earlier ones that ran clean, ruling out rm -f as the discriminator. The most likely trigger, by elimination, is the recursive cp -R with two variable paths — the same "critical path" textual-scan class as the documented rm -r behavior, just not limited to rm.

This matters for the ask above: if the check's scope is "any recursive filesystem verb with a variable operand," not just rm, then avoiding rm -rf (the only documented trigger) is not sufficient to keep an unattended agent from freezing — cp -R, and plausibly mv, cp -r, rsync, etc. with variable paths may trip the identical unbypassable, unobservable dialog. We were not able to confirm this from inside the session (the tool call returns as an ordinary success after the click, with no denial/prompt payload visible to the calling process), so this is inference from a diff of what passed vs. what didn't rather than a captured error.

If it's useful, happy to try to construct a minimal, isolated repro (a small script with a bare cp -R "$A" "$B" under bypassPermissions) rather than relying on this after-the-fact diff.

Ahmet-Dedeler · 21 days ago

+1

ynezz · 4 days ago

Same issue on 2.1.296, Linux, bypassPermissions. The prompts come from general-purpose subagents writing throwaway test scaffolding. Three prompts in one session:

rm -f "$c"/*          # c=$T/$1, T=$(cat "$TMPDIR/.state")
rm -rf "${c:?}"       # agent's "fix" after the first prompt
cd /deep/dir; rm -f ../../../../../file   # see #94704

The check is right each time. ${c:?} doesn't help because c is never empty, only T can be. That makes the main problem the feedback loop, not detection:

  • The human sees the rule, in the dialog. The agent never does, so it can't learn. The next subagent starts fresh and makes the same mistake.
  • The dialog suggests "use a literal path", which nudged the agent toward guarding the composed variable instead of the source variable.

Suggestion: in bypassPermissions (at least for subagents), return the check as a tool error to the model with the same explanation, instead of blocking on a human. The agent can rewrite the command (rm -rf "${T:?}/${name:?}", absolute path) and keep going unattended. Nobody's pane freezes, and the safety property still holds.

Our workaround for now: a CLAUDE.md rule that subagents load ("guard every variable at the rm site; no cd; rm ../..; never rm a $(...)/cat result directly").

Showing cached comments. Read the full discussion on GitHub ↗