False-positive deletion check: read-only compact Bash command flagged as running rm (2.1.291)
Summary
Claude Code 2.1.291 classified a read-only, composed Bash command as a shell -c script that runs rm, even though the submitted command contains no rm substring or removal command. In an interactive session with bypassPermissions, this caused a human permission prompt followed by a denial approximately 120 seconds later.
This report requests correction of the false-positive command analysis, not removal of critical-path deletion protection, automatic approval, or a way to bypass safety checks.
Environment
- Claude Code: 2.1.291
- Host: macOS arm64, Darwin 25.6.0
- Permission mode:
bypassPermissions, recorded in the session transcript - Launcher:
claude --dangerously-skip-permissions ... - Interactive terminal session running a support workflow
- Custom model gateway; the denial is explicitly attributed to a built-in Claude Code safety check
Observed behavior
The submitted tool call queried an Airflow DAG run and task-instance pages using an output-capture wrapper. The wrapper invokes its child with Python subprocess.run(command, capture_output=True, text=True, check=False), saves complete stdout/stderr, and prints a bounded artifact receipt. It does not request approval or perform removal.
The original submitted command contains zero occurrences of the substring rm. Its operations are configuration lookup, authentication-token acquisition, URL encoding, curl GET requests, jq projections, and a finite pagination loop.
The Bash call was submitted at 05:52:23.132Z. Claude returned the denial at 05:54:23.441Z: 120.309 seconds later. The command did not run. PreToolUse success attachments immediately before the denial did not contain a permission ask decision. No AskUserQuestion tool call occurred in this session.
Exact denial text
Permission for this command was denied by a built-in Claude Code safety check, not by the user. The check stops removals that can delete far more than intended: a system, home or workspace directory, or a target it cannot resolve, such as a shell variable that, if unset or empty, turns this into `rm -rf /` or `rm -rf /*`. Only a person may approve such a removal, and no person did (the permission prompt timed out, or this session cannot prompt). The command was NOT run; do not claim it succeeded. Do not work around the check by splitting, scripting, or re-issuing the removal through another tool or shell: the check exists because a removal like this can destroy the user's data, and getting past it would not make it safe. If the text below suggests a safe rewrite, run that instead; it goes through the same check. Otherwise finish the rest of the task without this removal, tell the user what you wanted to delete and why, and leave the removal to them. What was flagged: This command passes a shell -c script that runs rm, and Claude Code could not check the script for dangerous removals. Approve only if you have read the script.
Reproduction status and limits
This is a transcript-backed occurrence, not an independently established minimal reproducer. A syntax-only bash -n check of the original submitted command succeeds. We did not replay production requests or perform removals. Other output-wrapper commands succeeded in the same session, so using a wrapper or bash -c alone is not sufficient to establish the trigger.
The sanitized command below preserves the composed-command and quoting structure, but changes integration identifiers and dates. It depends on local configuration and authentication tooling and has not been verified as a standalone reproducer. Credentials and endpoint values are not included. No claim is made that these substitutions preserve the false-positive trigger.
Expected behavior
Preserve all existing critical-path deletion safeguards. Analyze this non-removing command without attributing an rm operation that is absent from the submitted text. If a different unsupported syntax or safety condition causes the denial, report that actual condition rather than a deletion operation.
Related: #93392 describes confirmation for actual variable-path removal. This report differs because the submitted command contains no removal operation.
Sanitized diagnostic command (not a verified standalone reproducer)
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${SUPPORT_PROJECT_ROOT:-$CLAUDE_PROJECT_DIR}/plugins/support-tools}"; SKILLS_ROOT="$PLUGIN_ROOT/skills"; COMPACT="$SKILLS_ROOT/airflow-api/scripts/compact_query.py"; CONFIG_GET=("$SUPPORT_PROJECT_ROOT/.venv/bin/python3" "$SKILLS_ROOT/_shared/skill_config.py" --skill airflow-api --skill-root "$SKILLS_ROOT/airflow-api"); CLUSTER_ALIAS=example-prod; BASE=$("${CONFIG_GET[@]}" --get "env.clusters.$CLUSTER_ALIAS.url") || exit 1; API_VERSION=$("${CONFIG_GET[@]}" --get "env.clusters.$CLUSTER_ALIAS.api_version") || exit 1; API_PREFIX="/api/$API_VERSION"; TOKEN=$(opencli airflow auth-token "$CLUSTER_ALIAS" --raw | head -1) || exit 1; AUTH=(-H "Authorization: Bearer $TOKEN"); RUN_ID='scheduled__2026-01-01T00:00:00+00:00'; RUN_ENC=$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$RUN_ID") || exit 1; python3 "$COMPACT" --label example-alert-run -- bash -c 'set -o pipefail; curl "${@:1:$#-1}" "${!#}" | jq '\''{dag_id, dag_run_id, state, logical_date, start_date, end_date, last_scheduling_decision}'\''' _ "${AUTH[@]}" "$BASE$API_PREFIX/dags/example_dag/dagRuns/$RUN_ENC"; for offset in 0 100 200; do python3 "$COMPACT" --label "example-tasks-$offset" -- bash -c '"'"'set -o pipefail; curl "${@:1:$#-1}" "${!#}" | jq '\''{total_entries, task_instances: [.task_instances[] | {task_id, state, try_number, start_date, end_date, duration, hostname, unixname} ]}'\'''"'"' _ "${AUTH[@]}" "$BASE$API_PREFIX/dags/example_dag/dagRuns/$RUN_ENC/taskInstances?limit=100&offset=$offset"; done