Approvals
An approval is a request that is allowed in principle but held until someone with the right role says yes. The action does not fail and does not need to be repeated. It waits, and runs once approved.
Go to Gateway → Approvals. Everyone in the workspace can see the page, and the sidebar shows how many requests are waiting.
When an action waits for approval
Section titled “When an action waits for approval”Nothing waits for approval until an admin says it should. Out of the box every granted ability runs as soon as it is called, and the rules list says “No approval rules. Every granted ability runs as soon as it is called.” An approval rule changes that for the calls it matches. Firefight parks the call and asks for a decision instead of running it.
Rules live under Gateway → Permissions. Choose Rules under Approval rules in the left column, then Add rule, and you need admin access. Rules are changed only by a person, on the dashboard or by confirming it in a Halon chat. The API (GET /approval_rules) and MCP can read them, and a change sent with a token is refused, so no token can loosen the rules that hold its own calls. The Add approval rule dialog asks:
| In the dialog | Choices |
|---|---|
| Which abilities | Pick specific abilities, or leave it on Any ability |
| Which risk levels | Tick write, destructive or both. Leave both unticked for any |
| Which environments | Tick environments, or leave them all unticked for any. Shown once your workspace has environments |
| Who approves | Anyone with the owner role, Anyone with the admin or owner role (the default), or Specific people or agents, which asks you to add a person, agent or service key |
| Agents may decide this rule | Shown once you name an agent or service key. Off by default, so the named agents and keys are listed but only the people decide |
| Whoever is on call may approve too | Off by default. The people the incident was escalated to can approve too, and get a direct message when nobody working the incident can approve |
| Where to ask | Default (the chat’s thread and approvers’ DMs), Incidents channel, Direct message to each approver or Both. A new rule starts on Default. Every request is also listed under Approvals, whichever you pick |
| Requester can approve their own request | On by default. Off means someone other than the requester has to decide |
Create rule saves it. The list then reads each rule as a sentence, such as “every destructive ability in Production”, with who approves it and where it is asked.
A call is held by the first rule that matches it, reading top to bottom, so put the narrow rules above the broad ones and use the arrows to reorder. Switch a rule off to keep it without enforcing it.
An ability covered by a rule shows an approval marker wherever it is handed out, so whoever is granting it knows it will wait.
On the dashboard, in Slack and in a Halon chat, two things never wait, whatever the rules say. Deciding an approval, and changing permissions or the rules themselves. Otherwise a rule could hold the screen you would use to remove it.
Over the API and MCP it works the other way round. A request or tool that changes permissions, member access, API keys, agents, approvals or webhooks always waits for an admin to approve it, whatever the caller was granted and whatever the rules say. So does a connection’s tool that changes who can reach something or sets a secret, such as setting a GitHub Actions secret, whenever an agent, a token or a workflow step calls it. The request is decided on the Approvals page or in Slack like any other, so a person sees every such change before it runs.
Halon saving, correcting or disputing something it remembers never waits either, so a broad rule cannot stop it remembering what you just told it. It still needs permission to change memory, and every change shows in Activity. Changing a memory yourself on the Memory page or from Slack follows your rules like any other change.
Reads never wait either. A rule holds only changes, so the dialog offers no read risk level and no ability that only reads, and saving a rule that names one says why it was refused. A call that only reads through a tool that can also change things, such as a GET through an API request tool, is a read too. A rule that names that tool holds the changes made through it and lets the reads through.
Where a request is asked
Section titled “Where a request is asked”Where to ask decides where Firefight posts the request in Slack. Every request is also on the Approvals page.
| Where to ask | A change Halon wants to make in a chat | Any other request |
|---|---|---|
| Default | A direct message to each approver. If none of them can get one, the incidents channel instead | The incidents channel |
| Incidents channel | The incidents channel | The incidents channel |
| Direct message to each approver | A direct message to each approver | A direct message to each approver |
| Both | The incidents channel and a direct message to each approver | The incidents channel and a direct message to each approver |
Default keeps Halon’s requests out of the shared incidents channel. A rule saved before Default existed keeps asking in the incidents channel.
A change Halon wants to make in a chat that lives in a Slack thread is also asked in that thread, whatever the rule says, so the people in the conversation see it. Approving it runs it straight away, as the person who asked Halon, and Halon tells the chat how it went. If you may approve the change yourself, confirming it in the chat is your approval. See Waiting for someone else’s approval.
A step of a fix Halon proposed is the exception. Approving it does not run it. The person who applied the fix runs it within an hour, after Halon reads how things stand now. See Investigations.
What the requester sees
Section titled “What the requester sees”Firefight tells the person or agent that made the request that it is waiting, and tells them again once it is decided. Nothing else is needed from them.
In Slack, the reply says who needs to approve it, that the request has been posted, and that Firefight picks it up again as soon as someone approves. When it is approved, Firefight carries the action out and posts a message saying who approved it and that it went ahead. If the approved action then could not be finished, the message says so and asks them to try again. If it is denied, the message says who declined and that nothing changed.
On the dashboard, the page they were on shows the same message. The decision arrives as a direct message in Slack, and the change they asked for is applied without them having to redo it.
Over the API, the response comes back as accepted rather than completed, with the message approval_required and an approval id. Once approved, the caller repeats the identical request with the header X-Approval-Id set to that id, and it runs. See Using the API.
Over MCP, the tool result says approval is required and gives the approval id. The agent retries the same call with approval_id once approved. See Connect AI agents.
An agent connected as you can also be the one watching. With search_approvals it sees a request that names you and tells you, so you can decide it on the Approvals page or in Slack. Deciding with approve_approval or deny_approval waits for an admin’s approval like any other change to approvals over MCP, and so does approving or denying over the API.
An agent can also decide under its own name. Give it a service key under Developer → API Keys, name that key as an approver on the rule, and switch on Agents may decide this rule. From then on the agent, connected with that key, can approve or deny what the rule holds through the API, and every decision is recorded under the agent’s name. Nothing asks the agent. It polls GET /approvals?status=pending and sees the requests that name it. A rule without that switch refuses a machine’s click even when the machine is named, so the default stays a person saying yes.
A request made with a token, over the API or MCP, or by an agent or service key, is approved only by a person on the Approvals page or in Slack. Approving it with any token is refused with a message saying so, so no token approves what a token asked for.
An approval covers exactly the request that was made, with the same details. It admits that request once. Changing anything about it, or calling again after it has run, is a new request and asks again. Over the API and MCP the caller has an hour from the approval to repeat the request. After that the approval lapses and shows as expired, and calling again asks again.
Deciding
Section titled “Deciding”Every pending request carries Approve and Deny in every place it was asked, in Slack and on the Approvals page. Decide from any of them, and the result is the same.
The request in Slack
Section titled “The request in Slack”In Slack, a request is one card that says in plain words who wants to do what, such as “Halon wants to: scale web to zero”. The line under it names the connection the call runs through, such as “Through Faylee (Northflank)”, so you know which account it reaches, and who may decide, either “Needs a workspace admin” or the people the rule named. When the rule lets whoever is on call approve, the line says so with the incident, such as “or whoever is on call for INC-12”.
The card has three buttons, Approve, Deny and Details. Details opens the call itself, with its action, the connection it runs through, its scope, its parameters and who asked.
Once someone decides, every copy of the card updates in place, so nobody approves the same request twice. Then it follows the call to how it went:
| The card says | When |
|---|---|
| Approved by Dana · working… | The approved call is running |
| Approved by Dana · Done | The call went through, with a link to what it made when there is one, such as a pull request |
| Approved by Dana · Failed | The call ran and did not go through, with why |
| Denied by Dana | Someone denied it. Nothing runs |
| Withdrawn | Nobody needs it any more, with why. The buttons go and nothing runs |
A request is withdrawn rather than decided when it is no longer needed before anyone decides it, for example when nobody decided it in time, when the workflow run that asked ended, or when a fix Halon proposed is cancelled while one of its steps is waiting. An approved fix step that nobody ran within the hour is withdrawn too, and its card says who approved it before it lapsed.
A change Halon wants to make in a chat on the dashboard is told to the person who asked by direct message, with Open the chat, once it is approved or denied.
On the dashboard
Section titled “On the dashboard”The Pending approvals table lists when each request was made, who asked by name, such as Halon, for Maya Ortiz when Halon asked in her chat, where it came from, the action with the connection beside it, its details and the access it requires, with Approve and Deny for anyone who may decide. A request you may not decide shows them greyed out, and hovering them says why. Each decision confirms itself with a message, such as “You approved Halon’s request. It runs now.”
Below it, Recently resolved lists the last 50 decisions with their status, who resolved them and when. A request that was withdrawn, or an approved request nobody ran within the hour, shows as expired. An approved fix step the person chose not to run shows as dismissed.
The search_approvals MCP tool also names the connection and its provider for each request. It and GET /approvals name who asked in principal_name, as the table does, beside principal, which keeps the kind and name together, such as api_key:Deploy bot.
If you press a button on a request you cannot decide, Firefight tells you why. Either the request is no longer pending, you are not one of its approvers, or the rule asks for someone other than the requester.
Workflow approvals
Section titled “Workflow approvals”A workflow’s Approval step asks for an approval too. Its request shows the question the workflow asks in place of an action, with only Approve and Deny, and it can also be decided on the run’s page. A person the step names can decide it even without the approvals permission. See Asking people.
Approving your own request
Section titled “Approving your own request”By default you can approve a request you raised yourself. That is deliberate. When your AI agent proposes an action and you approve it, you are the safety check, and asking a second person to confirm what you already read would only slow the incident down.
Whoever is on call
Section titled “Whoever is on call”At night the people who can approve are often asleep while the person on call is awake. Switch on Whoever is on call may approve too on a rule, and everyone an incident was escalated to can approve the requests that rule holds for that incident, alongside the rule’s own approvers.
When a request comes in and none of the rule’s approvers holds a role in the incident or made the request, Firefight also sends it to whoever is on call in a direct message. Someone escalated to while a request is still waiting gets it then, once, by the same rule. Escalating an incident, by hand or by Halon paging, is what makes someone on call for it. See Halon on call.
Switch Requester can approve their own request off on a rule and approval must come from someone else. The dashboard and Slack both refuse the requester’s own click in that case, with a message saying so.
Permission to decide
Section titled “Permission to decide”Approving or denying needs the approvals permission, held by admins and owners. It can be granted to a member like any other permission, and to an agent or service key, which then also needs to be named on the rule with Agents may decide this rule switched on. See Permissions.
The one exception is a workflow’s Approval step that names people. Each person it names can approve or deny that one request from Slack, the Approvals page or the run’s page without the permission. It gives them nothing else.
Every request and its outcome is also recorded in Activity, including the ones that are still waiting.