From d1c42616d5040338ebec5b33b1648379ad324095 Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 20 Aug 2026 06:46:23 -0500 Subject: [PATCH 1/4] docs: document the Files & Folders permission family The troubleshooting guide covered only the seven entitlement-gated services, and said that a missing permission means tccd never prompts and agterm never appears in the matching Privacy pane. Desktop, Documents, Downloads and removable/network volumes work differently: no entitlement or usage string takes part, the grant is per app and is never inherited, and it is set under Files & Folders or Full Disk Access. A user whose listing fails in ~/Downloads read the existing section and had reason to think he was in the unfixable class. Also names the diagnostic that separates the two causes: /bin/ls -la answers a privacy denial with "Operation not permitted" and ordinary permission bits with "Permission denied", while some ls replacements print the same wording for both. Related to #468 --- docs/troubleshooting.md | 22 +++++++++++++++++++ .../agterm/skills/agterm/troubleshooting.md | 10 +++++++++ 2 files changed, 32 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index bf8724f6..0065f222 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -132,6 +132,7 @@ Reload with **File ▸ Reload Config** or `agtermctl config reload`. The keybind To tell "never posted" from "posted but not shown", run `agtermctl notify "test"` and check two things: `agtermctl tree --json` — a rising `unseen` on the target session proves the command reached the notification path — and the log below, which now records every posted and every suppressed notification. - **A permission prompt carrying agterm's name, or a tool that cannot get one.** Command-line tools request Automation, Camera, Microphone, Contacts, Calendars, Reminders, Photos, Location, Bluetooth, local network, speech recognition, system administration and system audio recording *through* agterm, so the prompt names agterm rather than the tool. A dismissed prompt is not re-offered, the tool just keeps failing (`osascript` reports "Not authorized to send Apple events"). See [Why agterm asks for camera, microphone and the rest](#why-agterm-asks-for-camera-microphone-and-the-rest) for why, what a grant then covers, and where to change your answer. +- **A command cannot read `~/Downloads`, `~/Desktop` or `~/Documents`.** Those folders are protected by macOS itself, separately from the services above, and the grant is per application — another terminal reading them says nothing about agterm. See [A command cannot read ~/Downloads, ~/Desktop or ~/Documents](#a-command-cannot-read-downloads-desktop-or-documents). - **Agent-status glyph does not update.** Install the hooks from Help ▸ Install Agent Status Hooks…. For shell-integrated agents, start a fresh shell so the `source` line added to your shell rc takes effect. For Pi, restart it or run `/reload` so it loads `~/.pi/agent/extensions/agterm-status.ts`; Pi status is only installed when `~/.pi/agent` already exists. For OpenCode, restart it so it loads `~/.config/opencode/plugins/agterm-status.js`; the plugin installs only when `~/.config/opencode` already exists. The hooks call `agtermctl session status`, so `agtermctl` must resolve first (see above). - **Agent-status glyph updates the wrong session.** One session's glyph blinks while the work happens in another — typically when agents run inside tmux (or a tmux-backed session manager such as agent-deck). The working process inherited another session's `AGTERM_SESSION_ID`: the status hooks target whatever id is in their environment, and a long-lived daemon started from inside an agterm session (a tmux server is the usual carrier) captures that session's `AGTERM_*` variables into its global environment and passes them to every child it ever creates. Check `tmux show-environment -g | grep AGTERM` — if present, clear them with `tmux set-environment -g -r AGTERM_SESSION_ID` (and the other `AGTERM_*` names), then restart the affected panes. To avoid it, start such daemons with the variables scrubbed (`env -u AGTERM_SESSION_ID … `) or from a terminal outside agterm. @@ -192,6 +193,27 @@ obtain permissions at all, and it is worth knowing before approving something yo matching service, listed as agterm, for example Automation ▸ agterm. A dismissed prompt is not re-offered, so if a tool keeps failing after you dismissed one, that is where to fix it. +## A command cannot read ~/Downloads, ~/Desktop or ~/Documents + +`ls ~/Downloads` fails for a directory that belongs to you, and another terminal on the same Mac lists it +without complaint. + +Those three folders, along with removable and network volumes, are protected by macOS directly. It is a +different mechanism from the section above: no entitlement and no `Info.plist` string takes part, so nothing +about agterm's signature can suppress the prompt. What matters is that the grant is per application and is +never inherited. Approving kitty or Terminal says nothing about agterm, so a Mac where every other terminal +reads the folder can still refuse this one, and as with the services above a dismissed prompt is not +re-offered and the command just keeps failing. + +Grant it in System Settings ▸ Privacy & Security ▸ Files & Folders, where agterm appears with a switch per +folder once something in a session has asked. Full Disk Access covers all of them at once and accepts agterm +from the + button without waiting for a request, at the cost of giving every program you ever run in a +session that same reach — the section above covers what a grant gives away. + +To tell a privacy denial from ordinary permission bits, run `/bin/ls -la` rather than a replacement such as +`eza`: macOS answers the first with `Operation not permitted` and the second with `Permission denied`, and +some replacements print the same wording for both. + ## Reporting a problem Collect this before filing: diff --git a/plugins/agterm/skills/agterm/troubleshooting.md b/plugins/agterm/skills/agterm/troubleshooting.md index cae1fe2c..b92e51c7 100644 --- a/plugins/agterm/skills/agterm/troubleshooting.md +++ b/plugins/agterm/skills/agterm/troubleshooting.md @@ -147,6 +147,16 @@ further prompt, and a dismissed prompt is never re-offered (`osascript` keeps re to send Apple events"). The user changes the answer in System Settings ▸ Privacy & Security under the matching service, e.g. Automation ▸ agterm. This is macOS policy, not an agterm bug: do not file it. +### "a command cannot read ~/Downloads, ~/Desktop or ~/Documents" + +macOS protects those folders, plus removable and network volumes, on its own: a separate mechanism from the +services above, with no entitlement or usage string involved. The grant is per app and never inherited, so +another terminal listing the folder proves nothing about agterm. The user grants it in System Settings ▸ +Privacy & Security ▸ Files & Folders ▸ agterm, or gives agterm Full Disk Access, which covers all of them at +once. A dismissed prompt is never re-offered. `/bin/ls -la` separates the two causes: `Operation not +permitted` is the privacy denial, `Permission denied` is ordinary permission bits, and some `ls` replacements +print the same wording for both. This is macOS policy, not an agterm bug: do not file it. + ### "The agent-status glyph does not update" Install the hooks from Help ▸ Install Agent Status Hooks…. For shell-integrated agents, start a fresh shell From 7b22cbe7a1e4747305f602f5e60a442ed2fb5843 Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 20 Aug 2026 06:55:41 -0500 Subject: [PATCH 2/4] docs: correct the usage-string claim and the folder-access diagnostic macOS does define a usage-description string per protected folder; agterm simply ships none, so the prompt falls back to Apple's generic wording. Saying no string takes part reads as "nothing here is agterm's to change", which is wrong in the one respect that is: WezTerm ships two of these keys and names the terminal in its prompt. The diagnostic also had no operand. A privacy denial blocks reading the protected directory itself, not its entry in the parent, so a bare `/bin/ls -la` run from home lists Downloads normally and tells the reader nothing. --- docs/troubleshooting.md | 18 ++++++++++-------- .../agterm/skills/agterm/troubleshooting.md | 10 ++++++---- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 0065f222..107ea470 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -199,20 +199,22 @@ so if a tool keeps failing after you dismissed one, that is where to fix it. without complaint. Those three folders, along with removable and network volumes, are protected by macOS directly. It is a -different mechanism from the section above: no entitlement and no `Info.plist` string takes part, so nothing -about agterm's signature can suppress the prompt. What matters is that the grant is per application and is -never inherited. Approving kitty or Terminal says nothing about agterm, so a Mac where every other terminal -reads the folder can still refuse this one, and as with the services above a dismissed prompt is not -re-offered and the command just keeps failing. +different mechanism from the section above: no entitlement gates it, so nothing about agterm's signature can +stop the prompt from appearing. macOS defines an optional usage-description string per folder and agterm +ships none, so the prompt carries Apple's own generic wording rather than agterm's. What matters is that the +grant is per application and is never inherited. Approving kitty or Terminal says nothing about agterm, so a +Mac where every other terminal reads the folder can still refuse this one, and as with the services above a +dismissed prompt is not re-offered and the command just keeps failing. Grant it in System Settings ▸ Privacy & Security ▸ Files & Folders, where agterm appears with a switch per folder once something in a session has asked. Full Disk Access covers all of them at once and accepts agterm from the + button without waiting for a request, at the cost of giving every program you ever run in a session that same reach — the section above covers what a grant gives away. -To tell a privacy denial from ordinary permission bits, run `/bin/ls -la` rather than a replacement such as -`eza`: macOS answers the first with `Operation not permitted` and the second with `Permission denied`, and -some replacements print the same wording for both. +To tell a privacy denial from ordinary permission bits, run `/bin/ls -la ~/Downloads` — the real `ls`, +against the folder itself, rather than a replacement such as `eza`: macOS answers the first with `Operation +not permitted` and the second with `Permission denied`, and some replacements print the same wording for +both. ## Reporting a problem diff --git a/plugins/agterm/skills/agterm/troubleshooting.md b/plugins/agterm/skills/agterm/troubleshooting.md index b92e51c7..e4d1834e 100644 --- a/plugins/agterm/skills/agterm/troubleshooting.md +++ b/plugins/agterm/skills/agterm/troubleshooting.md @@ -150,12 +150,14 @@ matching service, e.g. Automation ▸ agterm. This is macOS policy, not an agter ### "a command cannot read ~/Downloads, ~/Desktop or ~/Documents" macOS protects those folders, plus removable and network volumes, on its own: a separate mechanism from the -services above, with no entitlement or usage string involved. The grant is per app and never inherited, so +services above, gated by no entitlement. The per-folder usage-description strings are optional and agterm +ships none, so the prompt carries Apple's generic wording. The grant is per app and never inherited, so another terminal listing the folder proves nothing about agterm. The user grants it in System Settings ▸ Privacy & Security ▸ Files & Folders ▸ agterm, or gives agterm Full Disk Access, which covers all of them at -once. A dismissed prompt is never re-offered. `/bin/ls -la` separates the two causes: `Operation not -permitted` is the privacy denial, `Permission denied` is ordinary permission bits, and some `ls` replacements -print the same wording for both. This is macOS policy, not an agterm bug: do not file it. +once. A dismissed prompt is never re-offered. `/bin/ls -la `, against the failing folder itself, +separates the two causes: `Operation not permitted` is the privacy denial, `Permission denied` is ordinary +permission bits, and some `ls` replacements print the same wording for both. Needing the grant is macOS +policy, not an agterm bug: do not file it. ### "The agent-status glyph does not update" From d553d7a341180fb702a5d14f312a9202c28dac58 Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 20 Aug 2026 11:20:09 -0500 Subject: [PATCH 3/4] docs: replace the absolutes in the Files & Folders section "The grant is never inherited" contradicted the paragraph below it, which says Full Disk Access gives every program in a session the same reach. Both were reaching for one fact: the answer is recorded against the application macOS holds responsible, so kitty's grant is not agterm's while every command inside agterm rides on agterm's. Two other statements were wider than the evidence. The absence of an entitlement gate means a missing entitlement cannot suppress this family's prompt, not that agterm's signature plays no part at all: TCC still names the client and persists the decision by the responsible app's signed identity. And errno is a strong clue rather than an oracle, since other policies can also answer EPERM, so /bin/ls "usually" tells the two apart. --- docs/troubleshooting.md | 19 ++++++++++--------- .../agterm/skills/agterm/troubleshooting.md | 17 +++++++++-------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 107ea470..73b2bc3f 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -199,12 +199,13 @@ so if a tool keeps failing after you dismissed one, that is where to fix it. without complaint. Those three folders, along with removable and network volumes, are protected by macOS directly. It is a -different mechanism from the section above: no entitlement gates it, so nothing about agterm's signature can -stop the prompt from appearing. macOS defines an optional usage-description string per folder and agterm -ships none, so the prompt carries Apple's own generic wording rather than agterm's. What matters is that the -grant is per application and is never inherited. Approving kitty or Terminal says nothing about agterm, so a -Mac where every other terminal reads the folder can still refuse this one, and as with the services above a -dismissed prompt is not re-offered and the command just keeps failing. +different mechanism from the section above: agterm is not sandboxed, and unlike the services listed there no +missing entitlement can suppress this family's prompt. macOS defines an optional usage-description string per +folder and agterm ships none, so the prompt carries macOS's own wording rather than agterm's. What matters is +that the answer is recorded against the application macOS holds responsible. Approving kitty or +Terminal says nothing about agterm, so a Mac where every other terminal reads the folder can still refuse +this one, and as with the services above a dismissed prompt is not re-offered and the command just keeps +failing. Grant it in System Settings ▸ Privacy & Security ▸ Files & Folders, where agterm appears with a switch per folder once something in a session has asked. Full Disk Access covers all of them at once and accepts agterm @@ -212,9 +213,9 @@ from the + button without waiting for a request, at the cost of giving every pro session that same reach — the section above covers what a grant gives away. To tell a privacy denial from ordinary permission bits, run `/bin/ls -la ~/Downloads` — the real `ls`, -against the folder itself, rather than a replacement such as `eza`: macOS answers the first with `Operation -not permitted` and the second with `Permission denied`, and some replacements print the same wording for -both. +against the folder itself, rather than a replacement such as `eza`. A privacy denial usually reads as +`Operation not permitted` and ordinary permission bits as `Permission denied`, and some replacements print +the same wording for both. ## Reporting a problem diff --git a/plugins/agterm/skills/agterm/troubleshooting.md b/plugins/agterm/skills/agterm/troubleshooting.md index e4d1834e..33ce9044 100644 --- a/plugins/agterm/skills/agterm/troubleshooting.md +++ b/plugins/agterm/skills/agterm/troubleshooting.md @@ -150,14 +150,15 @@ matching service, e.g. Automation ▸ agterm. This is macOS policy, not an agter ### "a command cannot read ~/Downloads, ~/Desktop or ~/Documents" macOS protects those folders, plus removable and network volumes, on its own: a separate mechanism from the -services above, gated by no entitlement. The per-folder usage-description strings are optional and agterm -ships none, so the prompt carries Apple's generic wording. The grant is per app and never inherited, so -another terminal listing the folder proves nothing about agterm. The user grants it in System Settings ▸ -Privacy & Security ▸ Files & Folders ▸ agterm, or gives agterm Full Disk Access, which covers all of them at -once. A dismissed prompt is never re-offered. `/bin/ls -la `, against the failing folder itself, -separates the two causes: `Operation not permitted` is the privacy denial, `Permission denied` is ordinary -permission bits, and some `ls` replacements print the same wording for both. Needing the grant is macOS -policy, not an agterm bug: do not file it. +services above, gated by no entitlement, and agterm is not sandboxed. The per-folder usage-description +strings are optional and agterm ships none, so the prompt carries macOS's own wording. The answer is +recorded against the app macOS holds responsible, so another terminal listing the folder proves nothing +about agterm. The user grants it in System Settings ▸ Privacy & Security ▸ Files & Folders ▸ agterm, or +gives agterm Full Disk Access, which covers all of them at once. A dismissed prompt is never re-offered. +`/bin/ls -la `, against the failing folder itself, usually tells the two causes apart: `Operation +not permitted` for the privacy denial, `Permission denied` for ordinary permission bits, and some `ls` +replacements print the same wording for both. Needing the grant is macOS policy, not an agterm bug: do not +file it. ### "The agent-status glyph does not update" From 1ee44cd96b75f2e03e3704a15553bb8fe4e4c6bd Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 20 Aug 2026 11:28:30 -0500 Subject: [PATCH 4/4] docs(backlog): record the missing Files & Folders usage strings Investigating #468 established the strings are not what gates folder access, so they fix nothing and were left out of the docs change. What remains is that the user is asked why agterm wants his Downloads and told nothing, which is worth keeping a note of even though it is not worth a signed-bundle change on its own. --- .../no-folder-usage-strings-in-info-plist.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docs/backlog/no-folder-usage-strings-in-info-plist.md diff --git a/docs/backlog/no-folder-usage-strings-in-info-plist.md b/docs/backlog/no-folder-usage-strings-in-info-plist.md new file mode 100644 index 00000000..73d8130c --- /dev/null +++ b/docs/backlog/no-folder-usage-strings-in-info-plist.md @@ -0,0 +1,25 @@ +--- +worth: later +where: agterm/Info.plist:37 +added: 2026-08-20 +--- +# Info.plist declares no usage string for the protected folders + +The sixteen `NSxxxUsageDescription` strings cover the entitlement-gated services and none of the +Files & Folders family. macOS defines an optional string per protected folder — +`NSDesktopFolderUsageDescription`, `NSDocumentsFolderUsageDescription`, +`NSDownloadsFolderUsageDescription`, `NSNetworkVolumesUsageDescription`, +`NSRemovableVolumesUsageDescription` — and with none present it falls back to its own copy, measured in +`TCC.framework/Versions/A/Resources/Localizable.loctable` as +`REQUEST_ACCESS_SERVICE_kTCCServiceSystemPolicyDownloadsFolder` = `“%@” would like to access +files in your Downloads folder.` So the user is asked why agterm wants his Downloads and told nothing. + +Every other family already explains hosted-CLI responsibility in its string, so the same pattern applies: +"Command-line tools you run inside agterm may request access to your Downloads folder through agterm." +WezTerm ships two of these written for exactly that case; Ghostty, kitty and iTerm2 ship none. + +Deferred because it fixes nothing. The strings are not a gate — agterm can obtain these grants today +without them — so this repairs no denial and adds no access, and the only change is five pieces of prompt +copy. It also cannot be proven by CI, which pins the entitlement set and asserts nothing about +`Info.plist`: confirming the custom text actually renders needs a signed build and a fresh TCC state by +hand. Surfaced investigating #468, where the missing strings turned out not to be the cause.