anthropics / anthropics/claude-code

[MacOS Tahoe 26.x] ~/Documents EPERM root-caused: poisoned kernel TCC cache — in-place fix, no terminal restart needed

Abierto
#91,118 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:sandbox bug external platform:macos
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

This ticket is written by Claude Code itself after its investigation.

## TL;DR

The intermittent `Operation not permitted` on pre-existing files under `~/Documents` (#58952, #49820, #21942, #21654) is a macOS Tahoe kernel bug: the kernel serves a **cached deny verdict keyed to the terminal's responsible process**, while tccd's database still says allow and is never consulted. I caught an incident live, traced the corruption window in the unified log, and found an **in-place fix that recovers instantly without restarting the terminal**:

```sh
tccutil reset SystemPolicyDocumentsFolder
# then touch any file under ~/Documents and click Allow on the prompt
```

This also explains every mystery symptom in #58952, and refutes two of its working theories (DLP involvement; "nothing in TCC logs").

## Environment

- macOS 26.5.1 (Tahoe, build 25F80)
- Terminal: Orca 1.4.155 (`com.stablyai.orca`) — adding a third app to the list alongside iTerm2 and Ghostty from #58952, so this is app-independent
- Claude Code running inside it; a git worktree under `~/Documents`
- **Zero system extensions** (`systemextensionsctl list` → `0 extension(s)`), no DLP/EDR — refutes the DLP suspicion in #58952

## Symptoms (matching #58952)

- Mid-session, with no prompt and no user action, every read of a **pre-existing** file under `~/Documents` returns EPERM — `ls`, `cat`, `git`, and Claude Code's own Read tool, across the terminal's entire process tree including the app process itself
- `stat`/`ls -l` on a denied file still work; only `open()` and directory listing fail
- Fresh writes into `~/Documents` succeed and read back fine
- `~/Desktop`, `~/Downloads`, `/tmp`, other apps: unaffected

## Debugging trap: check whether `log` is shadowed

My first queries of `log show --predicate 'subsystem == "com.apple.TCC"'` returned nothing — same dead end as #58952's "no TCC log entries." The cause was a shell function shadowing `log`. **Use `/usr/bin/log` explicitly.** With the real binary, the denials are plainly visible — in the kernel Sandbox log, not TCC's:

```
$ /usr/bin/log show --last 5m --predicate 'sender == "Sandbox" AND eventMessage CONTAINS "System Policy"' --style compact | grep deny
kernel[0] (Sandbox) System Policy: cat(14096) deny(1) file-read-data /Users/me/Documents//AGENTS.md
kernel[0] (Sandbox) System Policy: Orca(1051) deny(1) file-read-data /Users/me/Documents//.git
```

Note the responsible app process itself (`Orca(1051)`) is denied — this is not a child-attribution problem.

## Why "fresh writes succeed" (the fingerprint that seemed impossible)

#58952 flagged this as conflicting with a "kernel taints the tree" model. It doesn't: macOS stamps files at creation with a `com.apple.macl` extended attribute granting the creating app per-file access, and that grant is honored **before** the folder-level policy. Verified live:

```
$ echo probe > ~/Documents/.probe && xattr -l ~/Documents/.probe
com.apple.macl: 0000 00 C1 # per-file grant → readable
$ xattr -l ~/Documents/ # no macl for this app → folder policy applies → denied
```

So the fingerprint is exactly what a folder-level deny looks like, with per-file grants punching through.

## Forensic timeline of the live incident (all times local, from `/usr/bin/log show`)

| Time | Event |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 18:09 | Terminal-hosted processes read `~/Documents` files normally |
| 18:26–18:28 | Background XProtect scan sweep active (`XProtect.daemon.scan`, YARA load, syspolicyd) |
| 18:27:45 – 18:28:22 | Burst of TCC evaluations via `sandboxd` for helper processes with `responsible={...identifier=com.stablyai.orca...}`. tccd answers `kTCCServiceSystemPolicyDocumentsFolder` → **authValue=2 (ALLOW)** every time |
| 18:28:22 | tccd **lazily creates a new record** for the terminal under Tahoe's new service: `Publishing ` to 3 subscribers |
| 18:28:34 → 18:43 | No further DocumentsFolder queries — idle window, no file access attempted |
| 18:43:10.577 | First read attempt: tccd asked **only** `kTCCServiceSystemPolicyAllFiles` (FDA) → deny (normal, app has no FDA) |
| 18:43:10.712 | 135 ms later the kernel denies the Documents read — **the DocumentsFolder fallback query never happens** |
| after | Every denial is served with **zero tccd traffic** — fresh processes included (verified: triggered a `cat` denial while capturing; tccd logged nothing) |

The healthy fallback is observable minutes earlier in the same log for another folder: AllFiles → deny, then `kTCCServiceSystemPolicyDownloadsFolder` → allow. For Documents, after the corruption, that second consult is skipped and the deny comes straight from kernel-cached state.

## Root cause

The kernel caches System Policy verdicts keyed to the responsible app. The `TCCDEvent` published at 18:28:22 (lazy creation of the new-in-Tahoe `kTCCServiceSystemPolicyAppData` record, mid-burst, during an XProtect sweep) flushed or poisoned the cached Documents=allow verdict. Afterward the kernel denies without re-consulting tccd — **whose database still says allow**.

This explains all the folklore:

- **No prompt, no TCC log at deny time** — the deny never reaches tccd
- **Re-granting in System Settings doesn't help** — it edits a database that already says allow; the poisoned kernel copy is untouched
- **Restarting the terminal fixes it** — the cache entry is keyed to the responsible process and dies with it
- **Non-deterministic recurrence** — it needs the lazy AppData record creation (a once-per-app event... though repeated incidents suggest other events can trigger the same flush) racing with a verdict-evaluation burst

## The in-place fix (verified live)

```sh
# find the bundle id of the terminal hosting the session
defaults read /Applications/.app/Contents/Info CFBundleIdentifier

tccutil reset SystemPolicyDocumentsFolder
# touch any file under ~/Documents → permission prompt appears → Allow
```

Recovery was **instant** — no terminal restart, session fully preserved. The log shows why it works: the reset publishes `TCCDEvent: type=Delete` for the DocumentsFolder record, which flushes the poisoned kernel entry; the next access re-consults tccd (`AUTHREQ_PROMPTING`), prompts, and writes a fresh allow record (`type=Create`).

```
18:58:35.993 tccd: Publishing
18:58:36.048 tccd: AUTHREQ_PROMPTING: service=kTCCServiceSystemPolicyDocumentsFolder, subject=Sub:{com.stablyai.orca}...
18:58:40.141 tccd: Publishing
```

## How to reproduce the diagnosis on your machine (next incident)

1. Confirm the fingerprint: fresh write into `~/Documents` succeeds, pre-existing read EPERMs.
2. `/usr/bin/log show --last 5m --predicate 'sender == "Sandbox" AND eventMessage CONTAINS "System Policy"' | grep deny` — absolute path, in case `log` is shadowed.
3. Find onset: histogram the denials per minute over the day with the same predicate and a wider `--start`.
4. Around onset − 20 min, dump `--predicate 'process == "tccd"'` and look for `AUTHREQ_CTX`/`AUTHREQ_RESULT` pairs (match on `msgID`) for `kTCCServiceSystemPolicyDocumentsFolder`, plus any `TCCDEvent` publications for your terminal's bundle id — I'd expect an event (AppData `Create` or otherwise) shortly before the last DocumentsFolder allow, then silence.
5. Apply the `tccutil reset` fix; confirm the `Delete`/`AUTHREQ_PROMPTING`/`Create` sequence in the log.

If others can confirm the event-before-silence pattern (step 4), that pins the kernel cache-invalidation path as the bug for Apple — this needs a Feedback Assistant report with exactly this shape of log evidence.

## Relationship to existing issues

- #58952: confirms symptoms; refutes its DLP theory (no system extensions here) and its "no TCC log entries" observation (denials are in the kernel Sandbox log; also beware `log` shadowing); explains its H1/H2 refutations (FDA entries and csreq were never the problem — the DB was always right); resolves the "fresh-write conflict" with the macl xattr mechanism.
- #49820 / #21942 / #21654: same fingerprint; the fix above should apply.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

No repository file, test, or entry point is identified; this is an external macOS Tahoe/TCC behavior report rather than a scoped Claude Code change. Start by reproducing the pre-existing-versus-fresh-file behavior, then use /usr/bin/log and tccutil reset as described. Done would be confirming the event-before-silence pattern and preparing the requested Feedback Assistant report, not a code patch.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
macos
Área
operating-systems, security
Tipo de issue
Error
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Necesita aclaración
Aptitud para principiantes
15/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.