Exceptions
All exceptions live inproofrail.exceptions. The base class is ProofRailPolicyError; catching it catches every policy-related exception the SDK raises. A few exceptions extend Exception directly when they’re not policy decisions (timeouts, backend connectivity, auto-pause).
ProofRailPolicyError when you want to handle every policy-driven denial uniformly:
Exception subclass instead.
ActionDeniedError
Raised when a policy denies an action, when a human reviewer denies an approval, or when an approval times out without a decision. Also raised when fail_mode="deny" is active and the backend is unreachable for that action class.
Attributes
| Attribute | Description |
|---|---|
message | Human-readable summary of why the action was denied. |
policy_name | Which policy made the decision (default policy name or custom policy name). |
condition | What about the action triggered the denial — the policy’s decision_reason, the approver’s notes, or a timeout message like "Approval was not resolved within the configured timeout window." |
chain_context | Dict containing chain-level context. Includes chain_id and other state at the point of denial. |
remediation | Suggested fix (e.g., "update cumulative_financial_threshold_usd in init() or approve via dashboard"). |
docs_url | Link to the relevant docs page for this kind of denial. |
decision_source | Where the decision came from: "backend_evaluation", "local_fast_path", "human_approval", or "offline_stub". |
Distinguishing denial reasons
ActionDeniedError doesn’t have a single reason enum. To tell what kind of denial occurred, check decision_source and condition together:
| Scenario | decision_source | condition |
|---|---|---|
| Policy denied at the backend | "backend_evaluation" | Policy’s decision_reason |
| Policy denied locally via fast-path | "local_fast_path" | Policy’s decision_reason |
| Human reviewer clicked Deny | "human_approval" | The reviewer’s notes |
| Approval expired without a decision | "human_approval" | "Approval was not resolved within the configured timeout window." |
Backend unreachable and fail_mode="deny" | "offline_stub" | The fail-mode reason |
Example
Don’t suppress and continue
SuppressingActionDeniedError and retrying the same action accomplishes nothing — the policy will deny it again. If you catch it, either:
- Change the situation (lower amount, different recipient, different agent) and retry
- Escalate the work to a human
- Log the denial and continue with the next part of your workflow
message, remediation, and docs_url fields are built to be shown to your application’s user — they tell a human reading logs what to do.
PolicyViolationError
Raised when a policy condition is violated outside the normal action-decision path — for example, when chain-level cumulative limits are checked at chain start or during background reconciliation rather than at an individual action evaluation.
Inherits from ProofRailPolicyError. In practice, most application code can catch ProofRailPolicyError to handle this and ActionDeniedError uniformly.
ProofRailKillSwitchError
Raised when the organization’s kill switch is active and an action is attempted.
When raised
Whenever the kill switch is on for your org andrecord_agent_action is called. This is distinct from a regular ActionDeniedError so applications can show a different message (maintenance mode vs. policy violation).
Attributes
| Attribute | Description |
|---|---|
message | Default: "All agent actions are denied: organisation kill switch is active". |
organization_id | The org the kill switch applies to. |
reason | The reason the admin gave when activating the kill switch. |
Example
BackendUnavailableError
Raised when the SDK can’t reach the backend within backend_timeout_seconds AND the fail_mode for that action class is "deny". Inherits from Exception, not ProofRailPolicyError — it’s an operational issue, not a policy decision.
If fail_mode is "allow" for the action class, the SDK does NOT raise — it proceeds locally and buffers the event for later replay.
Example
ChainTimeoutError
Raised when a chain exceeds its configured runtime without completing. Inherits from Exception.
Long-running batch workflows should configure a longer timeout via SDK config. The exception is meant to catch chains that have hung — not chains that are doing legitimate long work.
ChainAutoPausedError
Raised when a chain hits one of the auto-pause limits — event count, chain duration, or token budget. Inherits from Exception.
See Policies for the default auto-pause thresholds.
Example
Handling patterns
Catch all policy denials, distinguish by source
Distinguish human denial from timeout
Log denials for analytics
Don’t catch what you can’t fix
ProofRailKillSwitchError and ChainAutoPausedError shouldn’t be caught and worked around in normal application code:
ProofRailKillSwitchErroris an admin saying “stop everything”. Honor it; don’t bypass.ChainAutoPausedErrormeans runaway behavior was detected. Stop and investigate; don’t retry blindly.
ActionDeniedError is the exception you’d routinely catch in application logic. BackendUnavailableError is the one to catch when you need explicit retry or queue handling.
Where to go next
SDK API
Type signatures for everything else.
Configuration
The init() options that drive these errors.
Human approval
Where approval-related denials come from.
Kill switch
Activating, resuming, and handling in code.