UX

Approval Fatigue Is Real: Designing Permission Prompts Agents and Humans Both Trust

Akshay Sarode 7 min read
Direct answer

A permission prompt keeps working when it says exactly what's being asked (which host, for what purpose, for how long) instead of a generic category, when related asks from one logical operation are batched into a single decision instead of five separate popups, and when the ask reaches the human wherever they already are, phone push or open dashboard tab, rather than waiting for them to remember to check. Every decision still gets logged, approved or denied, so even a fast yes stays visible in hindsight.

Watch someone approve agent actions for an hour and you'll see the pattern. The first three prompts get read carefully. The fourth gets a half-glance. By the tenth, they're clicking Allow before the dialog has finished rendering. This isn't laziness. It's what happens to any warning system that fires constantly with no difference in urgency between a routine file write and something that could actually cost money or leak data.

The rubber-stamp problem

The entire argument for putting a human in the loop is that a person can catch the one risky action buried among a hundred routine ones. That argument only holds if the human is actually reading each prompt. Approval fatigue is what happens when they stop: every "Allow?" dialog looks the same, so the brain starts pattern-matching on the shape of the interruption instead of its content, and the thumb moves faster than the eyes.

This is a known failure mode in any system that asks for repeated yes/no decisions without varying the stakes shown to the decision-maker. Security teams have seen it for years with alert fatigue in SOC dashboards; hospital staff have seen it with clinical alarm fatigue. Coding agents that ask permission for every filesystem write, every subprocess spawn, every outbound request are running the same experiment, just faster, because an agent can generate a dozen permission-worthy actions in the time it takes a human to read one prompt carefully.

The fix isn't fewer permission checks. Removing the checks removes the safety net. The fix is making each prompt carry enough information that reading it is worth the half-second it takes, and making sure the prompts that matter don't get drowned out by the ones that don't.

What the ask actually says

"Agent wants permission. Allow?" tells you nothing you can act on. You don't know what the agent is about to touch, why, or for how long the grant lasts. Faced with that dialog, there are exactly two rational responses: deny everything, which breaks the agent's ability to do useful work, or approve everything, which is the rubber-stamp behavior this post is about. Neither response required you to think about the actual risk.

A prompt that names the specific host, the specific purpose, and a time bound changes the calculus. "Wants network access to api.stripe.com for the next 10 minutes, to fetch this month's invoice totals" is something you can actually evaluate against what you know the agent is supposed to be doing. If that host is unexpected, or the stated purpose doesn't match the task you gave it, that's the signal worth stopping for. Bernato's capability tokens already scope the underlying grant this way: network, workspace_rw, subprocess, screen_capture, accessibility, and full_disk_access are each a distinct category, and agents declare advisory intents at spawn time, filesystem, network, subprocess, mail, memory, spend, mobile, so the runtime has material up front to render a specific prompt instead of a generic one. The category alone isn't the useful part. What makes the prompt worth reading is naming the specific target and duration within that category, right when it's being requested.

See the difference

Same underlying grant, two ways to phrase the ask. Flip the switch below to compare them side by side.

Try it
Show good prompt design
Toggle between a vague ask and a scoped one
Vague ask
Agent wants permission. Allow?

The vague version costs less to render and less to write. It also produces zero useful decisions over time, because there's nothing in it to evaluate against. The scoped version costs a bit more, both for the runtime to construct and for the human to read, and it's the version that's actually still doing its job on the fiftieth prompt of the day.

One decision, not five popups

A single logical operation often needs more than one grant. Wiring up a Stripe webhook handler might need network access to api.stripe.com, write access to a route file and two others, and permission to run the test suite as a subprocess. Firing three or four separate prompts for that one piece of work forces a choice: sit and babysit the terminal through every round, or start approving on reflex just to get through the queue. Either way, fatigue wins.

The better shape is one card that lists everything the operation needs, itemized so a specific piece can still be denied without blocking the rest: network access to api.stripe.com, write access to the three files, permission to run the test command. One decision, one context switch, full detail preserved underneath it. The human still gets full visibility into what's being requested. They just don't have to re-orient five separate times to grant work that was always one task in the agent's mind.

Where the ask lands

A prompt that says the right thing still fails if it shows up somewhere nobody's looking. Bernato's runtime surface, the bernatod daemon plus a React web dashboard plus an Expo mobile app, supports live PTY viewing and approvals from either a browser or a phone, which matters more than it sounds like it should. A desktop dashboard approval works well when you're already at the machine with the terminal open and watching the run happen live. It's a bad fit for the two hours a day you're not at that machine, because a browser tab you have to remember to check is a browser tab you forget about, and an agent that's blocked waiting on an approval that never comes isn't actually being supervised, it's just stalled.

A phone push notification inverts that. Instead of the human having to remember to check, the ask comes to wherever they already are. For a risky action that happens to fire while you're at lunch or in a meeting, that's the difference between a decision made with attention and a decision made forty minutes later by an agent that timed out waiting, or a decision made by whoever finally happened to glance at a dashboard. Same underlying request, same log entry either way. The channel is what determines whether a human sees it while it's still worth seeing.

Pre-authorizing what's already proven safe

Not every action deserves an interruption, and treating all of them equally is exactly what causes fatigue in the first place. If an agent has asked for network access to api.stripe.com from this project a dozen times and been approved a dozen times, asking a thirteenth time in the identical narrow scope isn't protecting anyone, it's just training the human to stop reading. Letting that specific, narrow pattern get pre-authorized frees up attention for the requests that are actually new: a different host, a broader scope, a subprocess command that hasn't been run before.

The word doing the work in that sentence is narrow. Pre-authorizing "network access, always" collapses back into the same generic grant that made the vague prompt useless. Pre-authorizing "network access to api.stripe.com, from this project" keeps the scoping intact; it just stops asking about the one thing that's already been proven boring. The bar for what counts as boring enough to pre-authorize should stay high and specific, or the whole mechanism becomes a backdoor around the scoping work from the previous section.

Agent Request Human Approve Deny Logged
Every request reaches a human, who approves or denies it. Both outcomes write the same log entry.

The log is what makes a fast yes survivable

Even with narrow scoping, batching, and the right delivery channel, humans will still sometimes approve faster than they should. That's not a failure of the design, it's a property of humans. What matters is that a fast yes doesn't disappear. Every permission ask and every decision, approved or denied, by whom, and on what device, is a row in Bernato's local activity log. That means a decision made in half a second is exactly as visible afterward as one made after five minutes of deliberation.

This is what turns rubber-stamping from a silent failure into a recoverable one. If someone's approving everything without reading it, that pattern shows up in the log as a string of fast approvals from the same device, and it's something a team can actually notice and talk about. Without the log, a bad habit of clicking Allow is invisible until something goes wrong. With it, the log is the check on the humans, the same way the permission system is the check on the agent.

DimensionGeneric askScoped ask
What it saysCategory only ("network access")Target, purpose, and duration named
What the human can evaluateNothing specific to react toWhether the target and purpose match the task
Likely response after a dozen promptsReflexive approvalContinued reading, because there's something new each time
Value in the activity log laterLow, every entry looks the sameHigh, entries are distinguishable and searchable

FAQ

What happens if the human doesn't respond to an approval request in time?

We haven't published a specific timeout behavior, but the principle that should govern it is straightforward: an unanswered request should default to denied, not approved. Fail-safe beats fail-open. An agent that's blocked waiting on a decision is in a known, inert state. An agent that got a silent auto-approve because nobody answered in time has taken an action nobody actually reviewed, which defeats the entire point of asking.

Can low-risk actions be pre-authorized so I'm not asked every time?

Yes, and this is the mechanism described above under pre-authorizing known-safe patterns. A narrowly scoped, repeatedly approved pattern, like network access to one specific host from one specific project, can skip the prompt on future occurrences. The scoping has to stay narrow for this to be safe: pre-authorizing a whole category defeats the purpose, pre-authorizing one specific target within that category doesn't.

What's the difference between the desktop dashboard approval flow and the mobile push approval flow?

Both surface the same underlying request and write the same row to the activity log, approved or denied, by whom, on what device. The difference is where your attention already is. The web dashboard is a good fit when you're at the machine watching the terminal live via PTY viewing. The Expo mobile app's push notification is a better fit for anything that happens while you're away from the desk, since it pulls you toward the decision instead of waiting for you to remember to check a browser tab.

Does batching related permission asks make it easier to miss something?

Not if the batch keeps each item itemized rather than collapsing them into one line. The goal of batching is to avoid five separate context switches for one logical operation, not to hide what's inside it. A well-built batch still lists network access, file writes, and subprocess execution as distinct line items, so any one of them can still be denied on its own.