October 28, 2026
Your AI Got a Yes. Can It Use It Twice?
Approval prompts are turning into cookie banners. I’d rather have fewer of them, and have each yes cover exactly one thing.
By NoHands AI
6 min read
It's late afternoon. Your coding agent asks "Allow this command?" for the tenth time today. You hit Enter. You didn't read it. By the tenth one, almost nobody does.
Now the part that bothers me more.
Say the command you approved was npm install left-pad. The install failed. The agent decided to fix it on its own and ran npm install left-pad --force && npm audit fix --force. You never saw that one. An hour earlier you'd clicked "Always allow," so the second command rode in on the first yes.
Or say you approved a $4.99 payment for a report. The request failed. The retry came back quoting $5.49, to a different address. The wallet didn't ask again, because you'd already said yes.
In both cases you approved one thing and something else ran.
Last week I wrote on X that approval prompts are turning into cookie banners. This post picks up where that one stopped: what a yes should actually cover, and where the check belongs.
You can't fix this by reading harder
More prompts mean less reading. That's how people work. If every prompt looks the same, you stop looking at them. Telling users to be more careful won't change that, and a sterner human doesn't scale.
What helps is fewer prompts, where each one means something.
Picture a dialog with three buttons: Allow once. Allow for this session. Always allow.
The first one is honest. You saw a thing, and you said yes to that thing.
The other two do something quieter. They turn one yes into something the agent can reuse later, for requests you haven't seen yet. It behaves like a credential. And the agent decides when to use it.
I get why those buttons exist. Nobody wants to click Allow forty times to run a test suite. But "session" and "always" are about time. They say nothing about what the agent may do with your yes during that time.
That's the gap I keep thinking about.
There's also a cost on the other side. If "Allow once" means ten more prompts, people stop choosing it. So whatever replaces those buttons has to ask less often, and only when the answer could plausibly be different.
What exactly did you say yes to?
When I approve something, I'm approving a specific request. Who gets paid. How much. For what. Which command, with which flags. Which version of the code.
So the yes should carry a fingerprint of that request. If the next request has the same fingerprint, the yes applies. If any field changes, it's a new question.
Here's roughly how I think about it. This is a sketch of an idea we're working through, not something we've shipped:
approval = {
action: "payment",
recipient: "0xAb…91",
amount: "4.99 USDC",
resource: "GET /report?id=123",
expires: "2026-10-28T12:10Z",
used: false
}
execute(req):
if fingerprint(req) != approval.fingerprint -> ASK AGAIN (show diff)
if approval.used or expired -> ASK AGAIN
mark used; runapproval = {
action: "payment",
recipient: "0xAb…91",
amount: "4.99 USDC",
resource: "GET /report?id=123",
expires: "2026-10-28T12:10Z",
used: false
}
execute(req):
if fingerprint(req) != approval.fingerprint -> ASK AGAIN (show diff)
if approval.used or expired -> ASK AGAIN
mark used; runThree things in there matter to me.
The fingerprint covers the fields a person would care about. Amount, recipient, resource, command. If one of them moves, the old yes doesn't stretch to cover it.
Picking those fields is the real design work. For a payment I'd include recipient, amount, asset and the resource being bought. For a shell command I'd include the exact command string, every flag, and the directory it runs in. A request ID or a timestamp shouldn't count, because those change on every call and would make the agent ask again for nothing. Get the list wrong in one direction and you're back to prompt spam. Get it wrong in the other and a real change slips through.
The yes works once. After it's used, it's gone.
The yes expires. An approval from this morning shouldn't sit around until tonight, waiting for a request that happens to match.
And when the agent has to ask again, the prompt should show only what changed. Something like:
Same report. Price changed 4.99 → 5.49. Recipient changed. Approve?
That's a prompt I'd actually read. It's short, and every line is new information. Compare it with the tenth full-screen "Allow this?" of the day.
We've written about this before for code. In our Merge Gate post, a new pull-request head invalidates the previous exact-head authorization. Same idea here, applied to any action.
Three ways a yes gets used twice
When I look at how an approval ends up covering something it shouldn't, I see three patterns.
Replay. The same yes runs twice. A payment goes through twice. A command runs twice. The fix is easy to say: a yes is single-use.
Drift. A retry or a self-fix changes the request but keeps the old yes. That's the left-pad story and the $4.99-to-$5.49 story. We covered retries in an earlier post, and the line I'd repeat here is: finding out what happened to the last purchase doesn't authorize the next one.
Spread. A sub-agent inherits the yes. In our post on delegation, a $12 budget turned into a $15 bill because three agents each treated the full allowance as their own.
All three are the same bug. The yes is attached to a session or a conversation, and anything that happens inside that session can lean on it.
Attach it to the request instead, and all three get easier to catch. A replay is the same request with a used yes. Drift is a different request. Spread is a different actor making a request nobody approved.
Put the check on the action
Here's the part I think gets skipped most often.
The prompt is what the human saw. The action is what actually runs. They're produced at different times, often by different pieces of software.
If the only check happens at the prompt, you're checking a description of the action. The model wrote that description. It can paraphrase. It can summarize. It can leave out the --force.
A payment example. The approve screen says "Pay $4.99 to Report Co." The signer receives raw transaction data. If the signer doesn't compare that data with the fields that were approved, the screen is decoration. The human approved a sentence. The signer signed bytes.
So the check that matters happens at execution time, on the thing that executes, against what was approved.
Same with commands. If the thing that spawns the process compares the exact string it's about to run with the string you approved, the --force gets caught there. Nobody has to notice it in a chat window.
My short list:
- Store the approval outside the model. The agent shouldn't be able to edit what you said yes to.
- Compare at the signer or executor, against the actual request.
- Single use.
- Expires.
- If something is unknown, stop. In our first post we called this "fail closed on unknown," and it applies here too.
None of that needs more prompts. It needs the prompt and the execution to agree on what was approved.
Where this shows up in what we build
I want to be careful here. Some of what I described above is how I'm thinking. Some of it is public. Here's what's public.
Merge Gate is a private pilot build, not a generally available service. As I wrote in the Merge Gate post, a new pull-request head invalidates the previous exact-head authorization. The decision is bound to the base, the exact head, the review identity, the policy and the evidence. Merge Gate doesn't perform the merge. The executor's job is to not merge a different state.
SignGate is in preview, with invitation access. It returns ALLOW, REQUIRE_APPROVAL, or DENY before an action proceeds, and payment is the first reference scenario. Its current decision contract includes a decision ID, policy version, reason codes, evidence and a signer directive. The sample response on our site shows a REQUIRE_APPROVAL decision carrying:
"signer_directive": { "action": "DO_NOT_SIGN", "until": "APPROVAL_RECEIVED" }"signer_directive": { "action": "DO_NOT_SIGN", "until": "APPROVAL_RECEIVED" }The line we use for that boundary: SignGate decides. Other systems execute.
What happens after approval is received, and whether an approval is bound to exact fields, single-use, or time-limited, we haven't documented publicly. So read the fingerprint sketch above as what we're thinking about, and not as a SignGate feature.
One yes, one request
That's the whole idea. A yes covers one request. Change the request and it's a new question. And the check has to live on the thing that runs the action.
So I'm curious how this looks from your side:
What's the last thing you approved without reading — and would you have caught it if the agent changed it before it ran?
NoHands AI — Infrastructure for agents that act.