macOS: TCC permission prompts recur on every auto-update (interactive CLI never uses ClaudeCode.app)

Status Open
Reported on v2.1.246
Maintainer reply None cached
Activity 0 comments · opened Aug 26, 2026

macOS: TCC permission prompts recur on every auto-update (interactive CLI never uses ClaudeCode.app)

Env: macOS 26 (Darwin 25.5.0), Apple Silicon, Claude Code 2.1.246, native installer.

Symptom

"2.1.245" would like to access files on a network volume. reappears after every auto-update.
The dialog is titled with the version number because the subject has no bundle.
Unanswered prompts queue: on 2026-08-25 four were raised (16:13, 20:14, 20:44, 21:45) before any was answered.

Cause

tccd records consent for the CLI keyed by PATH, not by signing identity:

Publishing <TCCDEvent: type=Create, service=kTCCServiceSystemPolicyNetworkVolumes,
  identifier_type=Path, identifier=~/.local/share/claude/versions/2.1.245>
BUNDLE_ATTRIBUTION: executable path ... resolves to attributed bundle: (null)
IDENTITY_ATTRIBUTION: ... type 1

(Ghostty in the same log is type: 0.) The binary is a bare Mach-O — codesign -dv reports
Info.plist=not bound. Since the install path carries the version (versions/<semver>) and
~/.local/bin/claude is a symlink that execve resolves, every update presents a brand-new TCC subject.

You already ship the fix, but only wire it to one entrypoint

ensureAppBundle() hard-links the running binary into
~/.local/share/claude/ClaudeCode.app/Contents/MacOS/claude (CFBundleIdentifier
com.anthropic.claude-code) and re-execs through it with macDisclaimResponsibility: true.
It is called only from the --bg-pty-host entrypoint, and skipped when the argv after --
contains --bg-spare. The interactive CLI never re-execs through the bundle.
Observed consequence: on this machine ClaudeCode.app/Contents/MacOS/claude was 12 versions stale
(2.1.234, mtime Aug 17) while the running version was 2.1.246.

Second, deeper problem: link() does not give a stable identity

ensureAppBundle uses link(), so the bundle path and the versioned path are two names for one inode.
For a hard-linked inode the kernel returns whichever NAME is currently in the vnode name cache.
Isolated reproducer (any Mach-O, two hard links, one long-running process, sampling proc_pidpath
on the SAME pid while stat-ing the other name in between):

observed reported basenames: {'nameB': 8, 'nameA': 12}

So even processes that DO go through the bundle can be attributed to the versioned path.
A single-name inode would fix it, but a full copy or an APFS clone placed at
ClaudeCode.app/Contents/MacOS/claude is killed on exec (rc=137), while the same clone runs fine
outside the bundle — consistent with the bundle carrying no _CodeSignature/CodeResources
(codesign --verify --strict on the .app fails with code has no resources).

Suggested fixes

  1. Call the bundle relaunch from the interactive entrypoint too, not only --bg-pty-host.
  2. Ship ClaudeCode.app as a properly sealed, signed bundle whose executable is its own file, so the

inode has exactly one name and TCC can key on the bundle identifier.

  1. Failing both, install to a stable path and keep the version out of the executable's pathname.

Note for Apple (separate)

proc_pidpath / any path-based TCC identity is ambiguous for a multiply-linked executable; consent
recorded as identifier_type=Path can therefore be attributed to a different name than the one used
at execve.

View original on GitHub ↗