evaluate returns anything other than allow, the response
carries a deny_code (short, stable machine string) and a
deny_reason (human-readable detail). Codes are stable across
versions so dashboards, alerting, and routing logic can pin to them.
The same codes appear on decision: hold and decision: escalate —
the code names why the engine reached that state, not whether the
action was outright denied. See the Glossary
for the four-value decision enum.
Engine codes (from evaluate)
These are returned by the rule engine itself.
Actor & role
deny
The actor’s ID is on the policy’s deny list. Decision:
deny.
Operator action: confirm whether the block is intended; if the
actor should be reinstated, remove them from the deny list.escalate
The actor’s ID is on the policy’s escalate list — every action
by this actor routes to a human reviewer. Decision:
escalate.
Operator action: review and resolve the queued item.escalate
The actor’s role is on the policy’s escalate list. Decision:
escalate. Operator action: same as ACTOR_ESCALATED but the
trigger is the role, not the individual identity.hold
The actor’s ID is on the policy’s hold list — actions wait for
manual release. Decision:
hold. Operator action: review
and release in the held queue.hold
The actor’s role is on the policy’s hold list. Decision:
hold. Same handling as ACTOR_HELD but the trigger is the
role.deny
The action class requires a verified actor identity
(
requires_verified_actor = true) but the request carried no
valid, in-binding actor_identity.v1 assertion — so the request
fails closed before authority evaluation. Decision: deny.
A self-asserted actor_id is not a trusted signal; this is a
missing-credential error, not a policy denial. Integrator action:
attach a top-level actor_identity assertion signed by a trusted
issuer, with subject.principal_id equal to actor_id and its
binding matching the request’s action_type, tenant, and
environment.Approvals
deny
The action requires N approvals; M were supplied. Decision:
deny. deny_reason carries the exact counts. Operator
action: collect the additional approvals and resubmit.deny
The action requires N approvals from a specific role (e.g.
controller); M matching approvals were supplied. Decision:
deny. deny_reason names the role and the counts.Risk
escalate
The computed risk score met or exceeded the policy’s escalate
threshold. Decision:
escalate. deny_reason carries the
score and threshold.hold
The computed risk score met or exceeded the policy’s hold
threshold. Decision:
hold. Operator action: review the
evaluated context and release or deny.Rate limits and windows
deny
A user-defined rate counter exceeded its configured maximum
inside the configured window. Decision:
deny by default
(a policy may override to hold or escalate). deny_reason
carries the counter name and the n/m ratio.deny
The action fell inside an active freeze window (e.g. quarter-end
close). Decision:
deny. deny_reason names the window.
Operator action: wait for the window to close, or grant an
override if policy permits.Policy resolution
deny
No policy rule matched the request and
fail_mode is closed
(the default). Decision: deny. Operator action: confirm
whether a matching rule should exist; if so, publish it.deny
No active constraint bundle is published for this org and
fail_mode is closed. Decision: deny. Operator action:
publish a bundle for the affected action class.deny
A rule matched but its template’s condition set could not match
any branch. Decision:
deny. Operator action: review the
template — usually indicates a constraint expects a context field
the caller did not provide.deny
The action class has The GitHub Action injects this automatically, so you only hit this
when calling
requires_state_snapshot = true (the default
for every action class) but the evaluate request did not include a
top-level state_snapshot. Decision: deny, returned with
HTTP 200 (a normal deny body, not a 4xx error). Caller action:
add a top-level state_snapshot to the request body — it is not
nested under context:/v1-evaluate directly. SDK callers should branch on
the response body’s deny_code, not on the HTTP status. Retry advice:
retry with the field added — this is a recoverable, input-shape
error, not a policy denial.hold
Two or more rules share the same
layer, priority, and
created_at — the engine refuses to choose between them.
Decision: hold (fail-safe — the action is not denied,
but it is not authorized either). Operator action: republish
one of the rules with a higher priority or a different
layer to break the tie.Deploy gate codes (from deployment.production)
These codes are emitted by the Production Change Protection V1 policy, which runs as a
pre-check on every deployment.production evaluation before the bundle
rules engine. They appear on the deny_code field alongside the normal
decision values.
deny
The active deploy gate policy rule explicitly denied the request.
Common causes:
permit.action_type does not match
deployment.production; an override block is present but
override_reason or authority_basis is missing. Decision:
deny. Retry advice: never — fix the input.deny
The actor’s role is not on the approved list for
deployment.production. Approved roles: admin, approver,
deploy_engineer. Decision: deny. Operator action: verify
the actor’s role in actorMetadata.role and re-submit, or
request a role change.deny
The permit’s
environment field does not match
context.environment, or context.environment is absent.
Decision: deny. Operator action: ensure the permit was
issued for the environment you are deploying to and that
context.environment is set.deny
No permit was provided, the permit is missing
permit_id, or
permit.verified is not true. Decision: deny. Operator
action: call POST /v1-verify-permit with the permit token
before submitting the deploy evaluation; pass the result back
as context.permit.deny
The permit’s
issued_at timestamp is older than the deploy gate’s
15-minute TTL (900 s), or issued_at is absent. This shorter TTL
applies to the deploy gate only; the default permit TTL is 1 hour. Decision: deny.
Operator action: re-issue a fresh permit and retry. Do not
reuse permit tokens across deploy attempts.escalate
An unknown or inconsistent state was detected — for example, a
malformed ISO-8601 timestamp in
evaluation_confirmed_at or
permit.issued_at, or evaluation_confirmed is absent.
Decision: escalate. A human reviewer must inspect the
context and authorise or deny before the deploy proceeds. This
code is emitted even in pilot (shadow) mode because an
inconsistent state means the evaluation itself cannot be trusted.allow
A valid override was applied — both
context.override.override_reason
and context.override.authority_basis were present and non-empty.
Decision: allow. The override short-circuits all other deploy
gate rules. The deny_reason field carries the approver ID and
reason for audit purposes. Treat override-approved evaluations as
high-priority audit events.API error codes (HTTP errors, not decisions)
These are notdeny_code values on a decision — they are
error_code values on a non-2xx HTTP response. They surface the
same way in client code: branch on the code, not the message.
error
Missing or invalid API key. HTTP
401. Check the Authorization
header and the key’s status in the console.error
Concurrent request rate exceeded. HTTP
429. Retry with
exponential backoff. Distinct from RATE_LIMIT_EXCEEDED (which
is a decision, not a transport error).error
Per-tier monthly evaluation quota exhausted. HTTP
429 with
plan, limit, and used fields in the body. Operator action:
upgrade the plan or wait until the next billing cycle.error
More than 1000 items in a
/v1/evaluate:batch request. HTTP
400. Split the batch.How to handle these in your client
deny_reason strings. The reasons are
human-readable and may change without notice; the codes are the
contract.
Stability
Codes in this page are stable across v1. New codes may be added; existing codes are not renamed or removed without a deprecation notice.Related
- Glossary — canonical decision values.
- Permit — what
allowproduces. - Governed close operations — end-to-end flow showing where deny codes appear in audits.