[BUG]Permissions system requires internal tool names (TaskStop) that are not discoverable; invalid names fail silently
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The permissions system requires users to know opaque internal tool names (e.g., TaskStop), but provides no way to discover them. Users who enter incorrect or partial names (e.g., "Stop Task", "stop", "kill-task") get no error, no warning, no suggestion — the rule simply does nothing.
Concretely: /permissions presents human-readable display names like "Stop Task" in its UI, but the underlying runtime matches deny rules against internal function names like TaskStop. A user adding a deny rule for "Stop Task" via the UI will see the rule saved — but it will never fire, because the display name does not match the internal name.
What Should Happen?
The core problem is: the permission system’s input surface is unvalidated and opaque. Users cannot discover valid tool identifiers, and invalid input fails silently. Any fix should address at least the minimum tier:
Minimum (validation):
When a deny rule matches no known tool name, the system should surface an error and list all valid tool identifiers. A developer writing "stop" naturally expects fuzzy matching to TaskStop — currently they get silent failure.
Better (discovery):
Provide a --list-tools flag or API endpoint so users and scripts can dynamically retrieve the canonical tool identifier list.
Ideal (ergonomics):
Support fuzzy matching, prefix matching, or accept the human-readable display names and description fields as aliases for tool identification.
Error Messages/Logs
Steps to Reproduce
- Run /permissions → add a deny rule for "Stop Task".
- Verify the rule appears in settings.
- Ask Claude to stop a background task.
- Claude successfully calls TaskStop — the deny rule does not block it.
- Run /permissions again → add a deny rule for "TaskStop" (the internal name, discovered by trial‑and‑error).
- Now TaskStop is correctly blocked.
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.158
Platform
Other
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
This is not an isolated confusion. Other developers have assumed natural names like "stop" would map to TaskStop because the UI shows "Stop Task". The silent failure caused a real user loss: a 40‑minute cold‑start process was killed because the developer thought they had blocked "stop" but no error or fallback existed.
The /permissions UI already knows the human‑readable names — it should either accept those as aliases or at least warn when a rule is added that will never match.
4 Comments
Confirming this from the matching side, and adding a discovery method that works today.
Why the silent failure happens. Permission rules match against the canonical tool identifier (the
namefield in the tool-use protocol, e.g.TaskStop,Bash,Read), with no alias table and no fuzzy/prefix matching. The/permissionsUI shows the human-readable label ("Stop Task"), but the runtime only ever compares your rule string to the canonical name. So aStop Taskdeny rule is saved, validates as well-formed, and silently never fires — exactly your repro. There is no place in the flow where "this rule will never match any known tool" is detected, so I agree the missing validation tier is the real bug.Discovery that already works (no trial-and-error needed). The canonical names the matcher uses are written to your session transcript. Every time a tool runs, the JSONL records a
tool_useblock whose.nameis the exact identifier deny matches against. So you can recover the correct string by running the tool once, then grepping the transcript:On my machine this returns the literal strings (
Bash,Read,Edit,TaskStop,AskUserQuestion,Skill, …) — i.e. the values a deny rule has to match character-for-character. That turns "guess until it fires" into "look it up". A--list-toolsflag would still be the right fix, but the data is already there in the logs.For the destructive-loss case specifically (the 40-min cold start that got killed): the dangerous property is that a UI-label rule fails silently — you only learn it didn't bind after the loss. A
PreToolUsehook is keyed on the same canonical name programmatically, so besides being able to block (exit 2), it can also log every time it fires and every time it sees a tool it doesn't recognize — which makes a mis-keyed rule visible before it costs you something, instead of after. A few lines insettings.json:Because the matcher runs on the canonical tool name (the same string from the transcript), there's no display-name mismatch, and the log line tells you it actually engaged.
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Thanks for closing this and confirming the bug!
One follow-up: could you confirm whether the /permissions dialog and/or the permissions docs page will be updated alongside the fix? The worry is that even after the code fix ships, future users will still face the same silent-failure pitfall unless:
A grep-on-JSONL workaround is useful for power users, but the average user who sees "Stop Task" in the UI should not need to mine session transcripts to write a working deny rule.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.