[BUG] macOS permission prompts identify Claude Code as a version number ("2.1.232") instead of "Claude Code" — root cause + fix

Status Open
Reported on v2.1.232
Maintainer reply None cached
Activity 5 comments · opened Aug 14, 2026
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:

  1. Security-aware users deny it, then have to go spelunking through ps output to discover the scary unnamed thing was software they installed on purpose.
  2. 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:

  1. Launch the agent/PTY child process via the bundle path rather than versions/<semver>, so TCC has an Info.plist in scope and records the grant by bundle ID.
  2. Give each version its own properly-sealed .app wrapper, if the versioned path must remain the exec target.
  3. At minimum, name the on-disk artifact claude-2.1.232 rather than a bare 2.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

  1. Install Claude Code on macOS.
  2. Start a session with the computer-use MCP tools enabled (any flow that drives the GUI).
  3. Let it attempt to control Terminal (or any app requiring Automation permission).
  4. 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_PLANNED by 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 above

View original on GitHub ↗

3 Comments

johnm7777 · 16 days ago

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 .app is assembled locally at install time and is incomplete: it contains only Contents/Info.plist and Contents/MacOS/claude, with no _CodeSignature/CodeResources. Because the embedded signature declares that resources must be present, verifying through the bundle path fails:

$ 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)

The identical bytes verify cleanly when addressed as a standalone Mach-O:

$ codesign --verify --strict ~/.local/share/claude/versions/2.1.232
  valid on disk
  satisfies its Designated Requirement
  -> exit 0

Same inode (link count 2), so this is one binary, not two — the difference is purely that codesign switches 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.plist to 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:

  • The binary does carry 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.
  • For anyone else who lands here while trying to work out whether the unnamed prompt is malware: codesign -dv only displays a signature and will happily print an Authority line for a modified binary. The check that actually validates the bytes is codesign --verify --strict <path>. Verifying via the versioned path (not the .app path) is what gives a meaningful answer here, for the reason above.
johnm7777 · 16 days ago

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 ps and the dialog string. tccd records it outright, and the log confirms both the requester and the mechanism:

service="kTCCServiceAppleEvents"
target_team_id="Q6L2SF6YDW"
target_identifier_type=1                    <-- 1 = Path, not Bundle ID
target_identifier="~/.local/share/claude/versions/2.1.232"

identifier_type=Path is direct confirmation of the root cause rather than a hypothesis about it.

2. Retracted an untested prediction. The original body asserted:

"Launch the identical bytes via ClaudeCode.app/Contents/MacOS/claude and macOS would read CFBundleName and display 'Claude Code'."

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 honors CFBundleName for 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 .app bundle is fine." It is not. It has the right CFBundleName, but it is a locally-assembled wrapper containing only Info.plist and 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 -dv under the heading "the signature is valid." -dv displays a signature; it does not validate one, and will happily print an Authority line for a modified binary. Replaced with codesign --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 -dv is not the check you want. Use:

codesign --verify --strict /path/to/binary        # validates the bytes
codesign -dvv          /path/to/binary            # then read the Authority line

and verify via the versioned path rather than the .app path, for the bundle-validation reason above.

johnm7777 · 13 days ago

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:

$ lsof -p <pid> | awk '$4=="txt" {print $NF}'
~/.local/share/claude/versions/2.1.233

~/.local/bin/claude is 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):

tccd  AUTHREQ_ATTRIBUTION: attribution={
  responsible={TCCDProcess: identifier=com.apple.Terminal, …},
  accessing={TCCDProcess: identifier=com.anthropic.claude-code, pid=6211,
             binary_path=~/.local/share/claude/versions/2.1.233}, …}

Note identifier=com.anthropic.claude-code there — 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 with identifier_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:

$ find ~/.local/share/claude/ClaudeCode.app -mindepth 1
Contents
Contents/MacOS
Contents/Info.plist
Contents/MacOS/claude

No _CodeSignature/CodeResources, so bundle-path verification still fails while the versioned path still verifies clean:

$ codesign --verify --strict ~/.local/share/claude/ClaudeCode.app/Contents/MacOS/claude
  code has no resources but signature indicates they must be present     (exit 1)
$ codesign --verify --strict ~/.local/share/claude/versions/2.1.233
                                                                         (exit 0)

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:

inode      links  mtime             size       path
236631159    2    2026-08-13 18:30  306111312  ClaudeCode.app/Contents/MacOS/claude
236631159    2    2026-08-13 18:30  306111312  versions/2.1.232
241996463    1    2026-08-14 17:28  306981408  versions/2.1.233   <- live, separate inode

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:

  • Suggested fix (1) — routing the child process through the bundle path — would today exec the previous version's bytes. The wrapper needs to be on the update path, not merely sealed.
  • The ps output in the original report shows the --bg-pty-host process being launched from ClaudeCode.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 name

macOS carries two names for a process and they disagree here, which makes it easy to mistake a cosmetic change for a fix:

$ ps -axo pid=,ucomm=,comm=
6211   2.1.233   claude
7447   2.1.233   claude
  • comm is argv[0] — chosen by whoever spawns the process. It reads claude only because the shell passes the command as typed, via the PATH symlink.
  • ucomm is the kernel's p_comm, set at exec from the executable's own filename. Still 2.1.233.

top reports the kernel name, so the #12433 symptom is live on this build:

$ top -l 1 -stats pid,command
6211   2.1.233

Reproducible check that argv[0] is purely cosmetic — three processes, all of them /bin/sleep:

d=$(mktemp -d)
ln -s /bin/sleep "$d/9.9.9"
ln -s "$d/9.9.9" "$d/mytool"
"$d/9.9.9" 300 & "$d/mytool" 300 & zsh -c "exec -a claude $d/9.9.9 300" &
ps -axo pid=,ucomm=,comm= | grep -E "9\.9\.9|mytool|claude"
ucomm=[sleep]  argv[0]=[9.9.9]     <- symlink named like a version
ucomm=[sleep]  argv[0]=[mytool]    <- symlink with a different name
ucomm=[sleep]  argv[0]=[claude]    <- argv[0] override

ucomm ignores the symlink name and ignores argv[0]; it follows the resolved executable's filename. The same holds for the real binary — spawned with a deliberately bogus argv[0] it reports that bogus name while its exec path remains versions/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 what p_comm copies at exec and what TCC falls back to when there is no bundle. So:

  • Setting a friendlier argv[0] would make ps and other argv-based listings read claude while top, Activity Monitor and the TCC dialog still say 2.1.233. That change would look like it fixed both issues and would fix neither.
  • Suggested fix (3) — giving the on-disk artifact a name containing "claude" — should fix both surfaces at once, since both read that filename. It still would not provide a stable identity across updates, so it would not stop the per-version TCC entries accumulating in Local Network / Full Disk Access that are described in #12433.

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_type records appear in tccd for this binary in the last three days — consistent with no new grant being created. Confirming the string would require tccutil 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.

Showing cached comments. Read the full discussion on GitHub ↗