Every team deviates from policy sometimes: an emergency change skips review, a prod fix bends the change window, a customer promise bends the refund rule. The risk isn't the deviation — it's the unrecorded deviation, found by an auditor six months later with no context, no approver, and no expiry. A policy exception workflow makes each deviation a structured, time-bounded, signed record: who asked, what they bypassed, why, for how long, and who approved.
Policy exception management workflow: exceptions are borrowed time
| Step | What happens | Why it matters |
|---|---|---|
| Request | Structured: policy, deviation, reason, duration | The auditor can reconstruct intent |
| Approve/deny | Named approver, with the policy text visible | Deviations are decisions, not accidents |
| Expire | Exception auto-closes at its end date | No exception outlives its justification |
| Renew | New request, new record | Extensions re-earn approval — nothing rolls over |
| Audit | One query lists every open exception | Your compliance answer is a dashboard, not a spreadsheet |
Step 1: the exception request is structured
A free-text "please allow this" thread is not a record. The request carries the policy, the deviation, and the justification up front:
#!/usr/bin/env bash
# exception.sh — file a policy exception with the decision layer
set -euo pipefail
POLICY_ID="${1:?policy id}"
DEVIATION="${2:?what deviates from the policy}"
REASON="${3:?business justification}"
DURATION="${4:-30d}"
EXPIRES_AT=$(date -u -d '+72 hours' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -v+72H '+%Y-%m-%dT%H:%M:%SZ')
actionbox ask \
"Policy exception: ${POLICY_ID}" \
--option approve="Approve for ${DURATION}" \
--option deny="Deny — comply with the policy" \
--context-json "[{\"type\":\"key_value\",\"items\":{\"policy\":\"${POLICY_ID}\",\"deviation\":\"${DEVIATION}\",\"reason\":\"${REASON}\",\"duration\":\"${DURATION}\",\"requested_by\":\"$(whoami)\"}}]" \
--expires "$EXPIRES_AT" \
--on-expire-json '{"type":"resolve","response":{"type":"single_choice","value":"deny"},"reason":"exception request expired"}' \
--callback-url https://ops.acme.com/actionbox/exception-callbackThe callback writes the approved exception into your register — with the decision id, so the "yes" is traceable to the approver and the reason.
Step 2: the approver sees the policy, not just the ask
An approver who can't see what they're overriding is signing blind. Attach the policy text to the request:
actionbox ask \
"Policy exception: ${POLICY_ID} (${DEVIATION})" \
--option approve="Approve for ${DURATION}" \
--option deny="Deny — comply with the policy" \
--context-json "[{\"type\":\"key_value\",\"items\":{\"policy\":\"${POLICY_ID}\",\"policy_text\":\"Change windows: production changes require 48h prior approval. Emergency changes may proceed, but must be documented within 24h.\",\"deviation\":\"Change applied outside the window; retro-approval requested.\",\"reason\":\"${REASON}\",\"expires\":\"${EXPIRES_AT}\"}}]" \
--expires "$EXPIRES_AT"Now "approved" means the approver weighed the actual rule against the justification — and the exception record quotes the rule, so the file survives people leaving the company.
Step 3: expiry is the point
A policy exception without an end date is a permanent rewrite of the policy by a side door. Expiry is what keeps exceptions exceptional:
Use the Actionbox History/API view to find open exceptions whose expiry has
passed, then cancel or resolve each Action by its ID. The CLI get command
requires one Action ID; there is no actionbox list command in the current
CLI surface.
The renewal is a new request: the exception does not roll over, and each extension re-earns an approver's signature. Audit evidence stays crisp — every open exception has a date it stops being true.
Full example: the compliance dashboard query
Use the dashboard History view or the documented user-scoped Actions API to review current exceptions by Source, owner, decision, and expiry. Keep the exception ID in your compliance register so renewal and revocation operate on the exact Action that was approved.
That list is your answer to "show me all current deviations from policy" — in an auditor's meeting, in one command, with the approval history one lookup away.
Why this beats "we've always done it that way"
- Deviations become decisions — each one has a named approver and a recorded reason
- Exceptions expire — nothing bypasses policy indefinitely by default
- Renewals are visible — the same exception re-approved twice is a policy problem, now detectable
- Audit is a query, not a scramble — open exceptions, owners, and dates on demand
Try it
- Install the CLI on your compliance or ops host
- Create a Source →
ACTIONBOX_TOKEN - Route exception requests through the ask pattern
Create a free Source · CLI + cron reference · Cron jobs that ask before acting · Refund approval workflow