claude-code - 💡(How to fix) Fix [BUG] fewer-permission-prompts drops commands prefixed with environment-variable assignments

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

The fewer-permission-prompts skill silently drops commands that are prefixed with environment-variable assignments (e.g. FOO=bar cmd ...), claiming they're "already covered" by an existing allowlist rule for the bare command. But Claude Code's Bash permission matcher is a literal prefix matcher and does not strip env-var prefixes — so the user still gets prompted every time. Over months, this accumulates dozens of redundant exact-match rules in settings.local.json that never consolidate.

Error Message

Alternatively: warn when the skill detects this mismatch (N commands grouped under uv run, but none match Bash(uv run:*) literally) so the user can decide.

Error Messages/Logs

Root Cause

Step 2 of the skill (Extract tool-call frequencies) says:

For Bash calls: parse input.command, take the leading command token (handling sudo, timeout, pipes, &&, env-var prefixes). Record the command + first subcommand pair (e.g. git status, gh pr view, ls, cat).

Stripping env prefixes is correct for grouping counts, but the skill then uses the stripped form to check "already covered" against the raw allowlist strings. The matcher's semantics differ from the extraction normalization, so "covered" in the skill means something the real matcher never sees.

Code Example

Scanned 20 recent transcripts in a project that had `Bash(uv run:*)` allowlisted. Of 13 distinct `TEST_DATABASE_URL=…` invocations that hit the permission prompt, none was suggested by the skill — all were mis-classified as "already covered":

| Variant | Count | Rule that would actually cover it |
|---|---|---|
| `TEST_DATABASE_URL="postgresql://postgres:test@localhost:55432/test" uv run…` | 6 | `Bash(TEST_DATABASE_URL="postgresql://…katlas_test" uv run:*)` |
| `TEST_DATABASE_URL="postgres://postgres:test@localhost:55432/test" uv run…` | 5 | same pattern, `postgres://` scheme |
| `TEST_DATABASE_URL="postgresql://postgres:test@localhost:55432/postgres" uv run…` | 1 | same pattern, different db |
| `TEST_DATABASE_URL="postgres://postgres:postgres@localhost:55432/postgres" uv run…` | 1 | same pattern, different creds |

The production `settings.local.json` in this project has ~90 exact-match entries that could collapse into 4 prefix rules using the proposed fix.
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Summary

The fewer-permission-prompts skill silently drops commands that are prefixed with environment-variable assignments (e.g. FOO=bar cmd ...), claiming they're "already covered" by an existing allowlist rule for the bare command. But Claude Code's Bash permission matcher is a literal prefix matcher and does not strip env-var prefixes — so the user still gets prompted every time. Over months, this accumulates dozens of redundant exact-match rules in settings.local.json that never consolidate.

Reproduction

  1. Allowlist Bash(uv run:*) in settings.
  2. Run TEST_DATABASE_URL="postgresql://host/db" uv run pytest tests/foo.py.
  3. You get a permission prompt despite Bash(uv run:*) being allowlisted.
  4. Click "allow" — an exact-match rule is added for that full invocation only.
  5. Run a second, near-identical pytest command with a different test path. Prompted again.
  6. Run /fewer-permission-prompts. The skill will ignore these commands because its extraction logic strips env prefixes and sees uv run pytest — already covered.

Root cause

Step 2 of the skill (Extract tool-call frequencies) says:

For Bash calls: parse input.command, take the leading command token (handling sudo, timeout, pipes, &&, env-var prefixes). Record the command + first subcommand pair (e.g. git status, gh pr view, ls, cat).

Stripping env prefixes is correct for grouping counts, but the skill then uses the stripped form to check "already covered" against the raw allowlist strings. The matcher's semantics differ from the extraction normalization, so "covered" in the skill means something the real matcher never sees.

Concrete evidence

Scanned 20 recent transcripts in a project that had Bash(uv run:*) allowlisted. Of 13 distinct TEST_DATABASE_URL=... invocations hitting the prompt:

VariantCountWould-be rule
TEST_DATABASE_URL="postgresql://postgres:test@localhost:55432/test" uv run…6Bash(TEST_DATABASE_URL="postgresql://…" uv run:*)
TEST_DATABASE_URL="postgres://postgres:test@localhost:55432/test" uv run…5same pattern, different scheme
TEST_DATABASE_URL="postgresql://postgres:test@localhost:55432/postgres" uv run…1same pattern, different db
TEST_DATABASE_URL="postgres://postgres:postgres@localhost:55432/postgres" uv run…1same pattern, different creds

All 13 were dedup'd away by the skill as "already covered by Bash(uv run:*)" — but none actually matched.

Suggested fix

In the dedup step, test each candidate command against the existing allowlist using the same semantics as the real Bash matcher (literal string with :* / * prefix-match) rather than the normalized-for-grouping form. Concretely: after grouping by stripped-command-token, before dropping a group as "already covered", re-check a sample of the raw commands in that group against the raw allowlist patterns. If none match literally, propose a rule for the group using the common literal prefix (e.g. Bash(TEST_DATABASE_URL="postgresql://…" uv run:*)), not the stripped form.

Alternatively: warn when the skill detects this mismatch (N commands grouped under uv run, but none match Bash(uv run:*) literally) so the user can decide.

Impact

Low-severity but persistent UX issue: users who run env-var-prefixed commands never get consolidated rules, only exact-match accretion. A production settings.local.json in my setup has ~90 entries that could collapse into 4 prefix rules.

Environment

  • Claude Code version: 2.1.114
  • macOS 25.2.0 (darwin arm64)

What Should Happen?

When the skill decides whether a group of commands is "already covered" by the existing allowlist, it should test against the raw commands using the same semantics as Claude Code's actual Bash matcher — literal prefix match (Bash(prefix:*) / Bash(prefix *)) and exact match, with no env-var stripping. If no raw command in the group literally matches any rule, the skill should propose a prefix rule built from the common literal prefix (e.g. Bash(TEST_DATABASE_URL="postgresql://…" uv run:*)) rather than dropping the group as covered.

Concretely: in step 2 of the skill (frequency extraction), stripping env-var prefixes for grouping is fine, but the subsequent dedup check against existing rules must use the unstripped form, because that's what the matcher sees.

Error Messages/Logs

Scanned 20 recent transcripts in a project that had `Bash(uv run:*)` allowlisted. Of 13 distinct `TEST_DATABASE_URL=` invocations that hit the permission prompt, none was suggested by the skill — all were mis-classified as "already covered":

| Variant | Count | Rule that would actually cover it |
|---|---|---|
| `TEST_DATABASE_URL="postgresql://postgres:test@localhost:55432/test" uv run…` | 6 | `Bash(TEST_DATABASE_URL="postgresql://…katlas_test" uv run:*)` |
| `TEST_DATABASE_URL="postgres://postgres:test@localhost:55432/test" uv run…` | 5 | same pattern, `postgres://` scheme |
| `TEST_DATABASE_URL="postgresql://postgres:test@localhost:55432/postgres" uv run…` | 1 | same pattern, different db |
| `TEST_DATABASE_URL="postgres://postgres:postgres@localhost:55432/postgres" uv run…` | 1 | same pattern, different creds |

The production `settings.local.json` in this project has ~90 exact-match entries that could collapse into 4 prefix rules using the proposed fix.

Steps to Reproduce

  1. In a project's .claude/settings.local.json, add Bash(uv run:*) under permissions.allow.
  2. Run a command like TEST_DATABASE_URL="postgresql://host/db" uv run pytest tests/foo.py — you get a permission prompt even though Bash(uv run:*) is allowlisted.
  3. Click "Allow" — Claude Code appends an exact-match rule for the full invocation, not a prefix rule.
  4. Run a near-identical pytest command with a different test path. Prompted again.
  5. Invoke the fewer-permission-prompts skill. It scans recent transcripts and reports "nothing to add" for these commands, because its extraction step strips the env-var prefix and sees uv run pytest — then marks the group "already covered" by Bash(uv run:*).

Repeating steps 2–4 over weeks produces dozens of exact-match entries in settings.local.json for every slight variation (different DB URL scheme, creds, test path, pytest flags). The skill never consolidates them.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.114

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

No response

extent analysis

TL;DR

The fewer-permission-prompts skill should be modified to test against raw commands using the same semantics as Claude Code's actual Bash matcher when deciding whether a group of commands is "already covered" by the existing allowlist.

Guidance

  • Modify the dedup step in the fewer-permission-prompts skill to re-check a sample of raw commands in each group against the raw allowlist patterns using literal prefix match and exact match semantics.
  • If no raw command in the group literally matches any rule, propose a prefix rule built from the common literal prefix.
  • Consider warning the user when the skill detects a mismatch between the grouped commands and the existing allowlist rules.
  • Review the settings.local.json file for exact-match entries that could be consolidated into prefix rules.

Example

No code snippet is provided as the issue is related to the logic of the fewer-permission-prompts skill and not a specific code implementation.

Notes

The proposed fix requires modifying the fewer-permission-prompts skill to use the same semantics as Claude Code's actual Bash matcher when checking for "already covered" commands. This may require changes to the skill's logic and potentially the underlying data structures used to store allowlist rules.

Recommendation

Apply the proposed workaround by modifying the fewer-permission-prompts skill to use the correct semantics when checking for "already covered" commands. This will help prevent the accumulation of redundant exact-match rules in settings.local.json and improve the overall user experience.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING