Request an Exception Inline
When a policy blocks something you believe is legitimate, you do not have to switch to the portal to ask for an exception. The denial itself carries the offer — reply to it, and AIControls files the request for you. See Approval Workflow for what happens after that.
Reply to the denial
A blocked chat request comes back as a normal assistant message naming the policy and, when an exception is available, telling you how to ask for one:
🚫 Request blocked — policy no-prod-credentials
Reading production credentials is not permitted from an agent session.
To ask an administrator for a time-boxed exception, reply
**request exception: <why you need it>** on the next line.
Nothing is granted until an administrator approves.
- Reply on the next turn with
request exception: <why you need it>. The phrase is case-insensitive, andrequest an exceptionworks too. Everything after the phrase becomes the justification your approver reads, so make it specific. - AIControls files the request and answers with its status instead of calling the model. The turn stays blocked — filing a request never runs the action you were denied.
- Wait for the decision. You are notified when it lands (see Notifications).
- Once approved, retry the original action. The exception is live for its granted window; nothing needs to be re-enabled.
The reply must be your very next message. If you carry on with other work and come back to it later, the offer has gone stale — trigger the same action again and reply to the fresh denial.
Why the phrase, and not just "yes"? Asking for an exception is an explicit, attributable act. A bare acknowledgement is too easy to produce by accident — or for injected content in a document or web page to produce on your behalf. The phrase makes the request deliberate.
Blocked model access
A request for a model outside your allowed set is offered the same way, and asks for the model rather than a policy:
🚫 Request blocked
Access to `claude-opus-4-6` is not in scope for your team.
To ask an administrator for a time-boxed exception, reply
**request exception: <why you need it>** on the next line.
Reply exactly as above. The request that lands in the approvals queue is a model access request for that specific model — see Request types.
Check on a request you already filed
Trigger the same blocked action again and the reply is a status update rather than a second offer:
⏳ Exception request pending — policy no-prod-credentials
Request ID: req_8f21c4
An administrator has been notified. This request stays blocked
until it is approved.
Once decided, the same turn reports the outcome and who decided it. You never accumulate duplicate requests by retrying: a second reply to the same denial re-surfaces the request you already have.
Your own pending and granted requests are also listed in the Builder Portal.
From an MCP tool call
MCP tool calls cannot show you a chat message to reply to, so a denied tool call comes back with a machine-readable block appended to the refusal, plus two governance tools your AI tool can call directly:
| Tool | What it does |
|---|---|
aigov__request_exception | Files the exception request. Takes the offer token from the denial, a required justification, and an optional requestedDuration (recorded in the justification for the approver — Phase 1 does not enforce it). |
aigov__check_request | Reports the status of a request you filed, by request id. It only ever returns your own requests. |
- Ask your AI tool to request an exception for the denied call. It reads the offer token out of the denial and calls
aigov__request_exceptionwith your justification. - The tool returns a request id and
"status":"pending". - Ask it to check the request (
aigov__check_request) when you want an update, or just wait for the notification.
These two tools are always visible, even under a policy that denies every other tool — the channel for asking about a block must not itself be blocked.
From the /request-exception command
Claude Code and other tools that install the AIControls skills get a /request-exception command. It is the universal fallback: it works whether or not the denial carried a structured offer.
- Run
/request-exception. With no argument it lists the policies that denied you this session and asks which one you mean. - Alternatively run
/request-exception <policy-name>to name the policy directly, or let the tool pass the pasted denial text — when the denial carried an offer, the policy (or model) is taken from it and you are not asked. - Enter your justification when prompted.
- The command waits for a decision — up to ten minutes — and prints it: approved by …, denied, or still pending — check later. It exits immediately if you would rather not wait.
Prefer replying to the denial or letting your tool pass the denial text along. When the offer travels with the request, the policy and target come from the denial itself, so there is nothing to look up and nothing to mistype.
When a call is held for approval
A policy set to Approve does not deny your call — it parks it while a reviewer decides, and your tool sits idle. If the reviewer declines, or nobody responds in time, you get a message that names the policy, repeats what the policy said, and includes the reviewer's note when there is one:
⛔ Not approved by reviewer — policy prod-deploy-approval
Deployments to production require a reviewer.
Reviewer note: use the change window on Thursday
An administrator reviewed this request and declined it, so it was not run.
A reviewer's decline carries no exception offer. An administrator has already
answered this request, so the message stands on its own and there is nothing to
reply to. If you think an exception is warranted anyway, ask through the Builder
Portal or with /request-exception — a decline closes the inline channel, not
every channel.
A hold that timed out, or one the approval queue could not accept, is different: nobody decided, so when the block is one you can request an exception for, the offer is appended to the same message and you can reply to it exactly as above.
An approved hold is silent — your call simply completes. The approval is recorded in the audit log rather than announced in the conversation.
Limits
- Only Claude and Gemini models on the native Amazon Bedrock and Google Vertex routes — and, on Bedrock, only the
InvokeModelAPI. Traffic your tool sends through/bedrock/*or/vertex/*gets the same inline offer, confirmation, and hold as every other route —CLAUDE_CODE_USE_BEDROCK=1andCLAUDE_CODE_USE_VERTEX=1are covered. Two exceptions: other Bedrock model families (Amazon Nova/Titan, Meta Llama, Mistral, Cohere, and similar), and Claude traffic sent through Bedrock's Converse API (converse/converse-stream) rather thanInvokeModel. A block on either path is still allowed or denied outright, with no inline offer. Ask through the Builder Portal or with/request-exceptioninstead, both of which work from any route. - Gemini/Vertex SDK clients specifically: the confirm-and-continue step (replying to accept an offer, or "yes" to a confirmation prompt) depends on your client faithfully round-tripping a hidden marker in the conversation history back to us. This is proven against our own test suite but not yet verified against a real Gemini client — if your client drops or rewrites that marker, you'll see the same prompt repeat instead of completing. The Builder Portal and
/request-exceptionalways work regardless. - An offer is good for ten minutes. After that, re-trigger the action to get a fresh one.
- You can have 5 unresolved requests at a time, and file 20 per hour. Beyond that, requests are refused with a plain message until some resolve or the hour rolls over.
- Filing a request is not a grant. Nothing changes until an admin approves it, and every request is attributed to you — the identity on your connection, never anything written in the conversation.