[BUG] macOS permission prompts identify Claude Code as a version number ("2.1.232") instead of "Claude Code" — root cause + fix
Edited after filing to correct three things in the original text: an untested prediction I had stated as fact, an inaccurate "the bundle is fine" summary, and a codesign invocation that displays a signature rather than validating one. Definitive TCC log evidence added. See the comments for the full change note.
Preflight Checklist
- [x] I have searched existing issues — see "Relationship to existing issues" below
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
macOS TCC permission prompts identify Claude Code by its version number rather than by name. The dialog reads:
"2.1.232" wants access to control "Terminal". Allowing control will provide access to documents and data in "Terminal", and to perform actions within that app.
There is no indication anywhere in the prompt that "2.1.232" is Claude Code.
This is worse than a cosmetic naming glitch, because of which permission is being requested. Automation control of Terminal lets the requesting process run arbitrary shell commands. A prompt asking for that much power, from a requester with no name, is indistinguishable from malware to any reasonable user. The correct security instinct — deny anything you can't identify — is the instinct that breaks Claude Code here.
The failure mode cuts both ways, and both are bad:
- Security-aware users deny it, then have to go spelunking through
psoutput to discover the scary unnamed thing was software they installed on purpose. - Everyone else learns to approve unnamed prompts requesting Terminal control. That is a genuinely harmful habit to train into a large user base, and it is the exact pattern real macOS infostealers rely on.
I hit this while an agent was running with the computer-use MCP tools (mcp__computer-use__*), which need Automation permission per target app — so this now fires during a normal, sanctioned workflow rather than as a rare edge case.
Root Cause
macOS is recording and displaying this permission by path instead of by bundle identifier, because the process that requests it is a bare executable with no enclosing bundle.
This is confirmed directly by the OS, not inferred. From the unified log (tccd), the actual permission request:
service="kTCCServiceAppleEvents"
target_team_id="Q6L2SF6YDW"
target_identifier_type=1 <-- 1 = Path (not Bundle ID)
target_identifier="~/.local/share/claude/versions/2.1.232"
function="TCCAccessRequestIndirect"
and the resulting TCC entry:
Publishing <TCCDEvent: type=Create,
service=kTCCServiceAppleEvents,
identifier_type=Path,
identifier=~/.local/share/claude/versions/2.1.232>
identifier_type=Path is the whole bug. TCC normally identifies an app by bundle ID and displays its CFBundleName; with no bundle in scope it falls back to path, and the string it shows the user is the last path component — which here is the version number.
The binary itself is correctly signed and correctly entitled, so this is purely an identity/display problem, not a permissions-model problem:
$ codesign --verify --strict ~/.local/share/claude/versions/2.1.232
valid on disk
satisfies its Designated Requirement (exit 0)
$ codesign -dvv ~/.local/share/claude/versions/2.1.232
Identifier=com.anthropic.claude-code
Authority=Developer ID Application: Anthropic PBC (Q6L2SF6YDW)
TeamIdentifier=Q6L2SF6YDW
$ codesign -d --entitlements :- ~/.local/share/claude/versions/2.1.232
com.apple.security.automation.apple-events = true
A correctly-named .app wrapper does exist alongside it, and is a hardlink to the very same binary:
$ ls -li ~/.local/share/claude/versions/2.1.232 \
~/.local/share/claude/ClaudeCode.app/Contents/MacOS/claude
236631159 -rwxr-xr-x 2 user staff 306111312 ClaudeCode.app/Contents/MacOS/claude
236631159 -rwxr-xr-x 2 user staff 306111312 versions/2.1.232
Same inode, link count 2 — one binary, two paths. The bundle carries the right metadata:
$ PlistBuddy -c "Print :CFBundleName" ClaudeCode.app/Contents/Info.plist
Claude Code
$ PlistBuddy -c "Print :CFBundleIdentifier" ClaudeCode.app/Contents/Info.plist
com.anthropic.claude-code
But that wrapper is not a complete bundle. It is assembled locally at install time and contains only Contents/Info.plist and Contents/MacOS/claude, with no _CodeSignature/CodeResources. Because the embedded signature declares that resources must be present, verification through the bundle path fails even though the bytes are identical:
$ codesign --verify --strict ~/.local/share/claude/ClaudeCode.app/Contents/MacOS/claude
-> exit 1
$ spctl -a -vv ~/.local/share/claude/ClaudeCode.app
ClaudeCode.app: code has no resources but signature indicates they must be present
origin=Developer ID Application: Anthropic PBC (Q6L2SF6YDW)
And the process that actually raises the prompt is spawned from the versioned path, not from the wrapper:
$ ps auxww | grep claude
user 82080 .../ClaudeCode.app/Contents/MacOS/claude --bg-pty-host ... \
-- ~/.local/share/claude/versions/2.1.232 --session-id ... [computer-use tools]
user 82140 ~/.local/share/claude/versions/2.1.232 --session-id ... [computer-use tools]
$ file ~/.local/share/claude/versions/2.1.232
Mach-O 64-bit executable arm64
What Should Happen?
The prompt should identify the requester as Claude Code (optionally "Claude Code (2.1.232)", which stays useful when several versions are installed).
Possible directions, but note the caveat below:
- Launch the agent/PTY child process via the bundle path rather than
versions/<semver>, so TCC has anInfo.plistin scope and records the grant by bundle ID. - Give each version its own properly-sealed
.appwrapper, if the versioned path must remain the exec target. - At minimum, name the on-disk artifact
claude-2.1.232rather than a bare2.1.232, so even the path fallback contains the word "claude".
Caveat on (1), which I have not tested: routing through the bundle path would give TCC something to read, but it also starts exercising bundle-level validation against a wrapper that currently cannot satisfy it (see the spctl output above). Whether macOS honors CFBundleName for display when the bundle fails resource validation is exactly the thing someone with the packaging context should check before treating (1) as a one-line fix. The incomplete wrapper may also be why the versioned path is the exec target today.
Whatever the fix, it would likely also resolve #12433, which is the same path-instead-of-identity root cause surfacing elsewhere.
Steps to Reproduce
- Install Claude Code on macOS.
- Start a session with the computer-use MCP tools enabled (any flow that drives the GUI).
- Let it attempt to control Terminal (or any app requiring Automation permission).
- The TCC prompt appears attributed to
"2.1.232"rather than to Claude Code.
To watch it happen in the log (note log is a zsh builtin on some setups, hence the absolute path, and TCC logs at info/debug level so those flags are required):
/usr/bin/log stream --info --debug --style compact \
--predicate 'process == "tccd" AND eventMessage CONTAINS "kTCCServiceAppleEvents"'
Also reproducible without computer-use: the same version-number-as-process-name appears in Activity Monitor and breaks tmux's automatic-rename (see #12433).
Relationship to existing issues
- #62827 — same bug, filed 2026-05-27 against 2.1.152, closed as
NOT_PLANNEDby the inactivity bot on 2026-06-27 with no human triage. Its closing message invites reopening if still relevant: it is, ~80 versions later. Neither that report nor its thread identified the cause. - #12433 — open, same underlying cause, surfacing in Activity Monitor and tmux instead of TCC. Confirmed still occurring across 2.0.76, 2.1.7, and now 2.1.232.
Filing fresh rather than commenting on the closed one, per the stale bot's instruction, and adding the root-cause analysis neither thread has.
Claude Code Version
2.1.232
Platform
macOS (Apple silicon, arm64)
Operating System
macOS 26.5.2 (build 25F84)
Terminal/Shell
Terminal.app / zsh
Additional Information
Install layout — the bare version-named executables are what every spawned process is identified by:
~/.local/share/claude/
├── ClaudeCode.app/Contents/{Info.plist, MacOS/claude} <- right CFBundleName, incomplete bundle
└── versions/
├── 2.1.227
├── 2.1.228
├── 2.1.231
└── 2.1.232 <- hardlink to the binary aboveShowing cached comments. Read the full discussion on GitHub ↗
3 Comments
Follow-up — an important qualification to suggested fix (1) above.
I said "the correctly-named bundle already exists and ships today," which is true about its name but not about its packaging. The wrapper
.appis assembled locally at install time and is incomplete: it contains onlyContents/Info.plistandContents/MacOS/claude, with no_CodeSignature/CodeResources. Because the embedded signature declares that resources must be present, verifying through the bundle path fails:The identical bytes verify cleanly when addressed as a standalone Mach-O:
Same inode (link count 2), so this is one binary, not two — the difference is purely that
codesignswitches into bundle-verification mode when the path sits inside a.app, and the locally-built wrapper has no resource manifest for it to check.So fix (1) is probably not a one-line launch-path change after all: routing the child process through the bundle path would give TCC an
Info.plistto read, but it would also start exercising bundle-level validation against a wrapper that currently can't satisfy it. The wrapper likely needs to be properly signed/sealed (or shipped pre-signed rather than assembled on the client) for that route to be safe.Worth flagging because it may also explain why the versioned path is the exec target today.
Two smaller notes for whoever picks this up:
com.apple.security.automation.apple-events, so the entitlement side is correct — this really is only an identity/display problem, not a permissions-model problem.codesign -dvonly displays a signature and will happily print an Authority line for a modified binary. The check that actually validates the bytes iscodesign --verify --strict <path>. Verifying via the versioned path (not the.apppath) is what gives a meaningful answer here, for the reason above.Change note — I have edited the issue body. Recording what changed so the thread stays honest, since the earlier text made a claim I could not back up.
1. Added definitive evidence. The original report inferred the requester from
psand the dialog string.tccdrecords it outright, and the log confirms both the requester and the mechanism:identifier_type=Pathis direct confirmation of the root cause rather than a hypothesis about it.2. Retracted an untested prediction. The original body asserted:
I never tested that, and it may well be wrong, because the wrapper bundle fails resource validation (
spctl: code has no resources but signature indicates they must be present). Whether macOS honorsCFBundleNamefor display purposes when the bundle cannot validate is precisely the open question. It is now marked as an untested caveat rather than stated as fact.3. Corrected "the
.appbundle is fine." It is not. It has the rightCFBundleName, but it is a locally-assembled wrapper containing onlyInfo.plistand the hardlinked binary, with no_CodeSignature/CodeResources. Suggested fix (1) was softened accordingly — it is likely not the one-line launch-path change I implied.4. Fixed the signature evidence. The original showed
codesign -dvunder the heading "the signature is valid."-dvdisplays a signature; it does not validate one, and will happily print an Authority line for a modified binary. Replaced withcodesign --verify --strict, which checks the code hashes and returns exit 0 here.Point 4 is worth repeating for anyone who lands on this issue while trying to work out whether an unnamed prompt on their own machine is malware, since that is the situation this bug creates.
codesign -dvis not the check you want. Use:and verify via the versioned path rather than the
.apppath, for the bundle-validation reason above.Re-checked on 2.1.233 (current
latest). Still present. Two updates: a new packaging finding, and a correction to a process-name observation I nearly reported as an improvement — it wasn't one, and getting it wrong would have pointed at a fix that resolves neither issue.1. Root cause unchanged
The running process still execs the bare version-named file, with no bundle in scope:
~/.local/bin/claudeis a symlink directly to that file, so a normal shell launch resolves to the same bundle-less path. TCC sees the same thing — an attribution record from today (elided,~substituted):Note
identifier=com.anthropic.claude-codethere — TCC does have the code-signing identifier for the process. That is not in tension with the report: the AppleEvents grant in the original evidence was recorded withidentifier_type=Path, and the dialog's display name is resolved separately. See "not re-verified" at the bottom.The wrapper is still incomplete. The entire bundle is two files:
No
_CodeSignature/CodeResources, so bundle-path verification still fails while the versioned path still verifies clean:2. New: the wrapper no longer tracks updates — it points at the previous version
In the original report the wrapper was a hardlink to the current binary. It is now stale:
The wrapper's binary shares an inode with 2.1.232, while 2.1.233 is what actually runs. 2.1.233 landed about seven hours after the wrapper was last written, and the wrapper was not refreshed then, nor on any launch in the three days since.
Two consequences worth flagging:
psoutput in the original report shows the--bg-pty-hostprocess being launched fromClaudeCode.app/Contents/MacOS/claude. If that is still a live launch path, a stale wrapper means that helper is a different build from the session that spawned it.3. Correction:
argv[0]vs the kernel process namemacOS carries two names for a process and they disagree here, which makes it easy to mistake a cosmetic change for a fix:
commisargv[0]— chosen by whoever spawns the process. It readsclaudeonly because the shell passes the command as typed, via the PATH symlink.ucommis the kernel'sp_comm, set at exec from the executable's own filename. Still2.1.233.topreports the kernel name, so the #12433 symptom is live on this build:Reproducible check that
argv[0]is purely cosmetic — three processes, all of them/bin/sleep:ucommignores the symlink name and ignoresargv[0]; it follows the resolved executable's filename. The same holds for the real binary — spawned with a deliberately bogusargv[0]it reports that bogus name while its exec path remainsversions/2.1.233, i.e. the binary does not rename itself.Why this matters for triage: #12433 and this issue have a single shared dependency — the executable is a file literally named
2.1.233, which is both whatp_commcopies at exec and what TCC falls back to when there is no bundle. So:argv[0]would makepsand other argv-based listings readclaudewhiletop, Activity Monitor and the TCC dialog still say2.1.233. That change would look like it fixed both issues and would fix neither.Not re-verified
The literal dialog string on 2.1.233. The Automation grant already exists here so it does not re-prompt, and no
TCCDEvent/identifier_typerecords appear intccdfor this binary in the last three days — consistent with no new grant being created. Confirming the string would requiretccutil reset AppleEvents, which clears every application's Automation grants rather than just this one. The path-based identity that produces the string is demonstrably unchanged, but I am marking the string itself as inferred rather than freshly observed.