anthropics / anthropics/claude-code
[MacOS Tahoe 26.x] ~/Documents EPERM root-caused: poisoned kernel TCC cache — in-place fix, no terminal restart needed
- Ngôn ngữ chính
- Python
- Star
- 145k
- Fork
- 23.1k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
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.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- macos
- Lĩnh vực
- operating-systems, security
- Loại issue
- Lỗi
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Cần làm rõ
- Mức phù hợp với người mới
- 15/100