Skip to content

ApprovalNotFoundError

runtime.approve(approval_id) was called with an id that doesn't match any pending approval in this runtime. The framework refuses to no-op an unknown id — that would silently corrupt the approval audit trail.

Multi-inherits LookupError for back-compat: code that does except LookupError around the approval API continues to work.

Quick fix

List currently-pending approvals and confirm the id matches:

for pa in rt.pending_approvals():
    print(pa.id, pa.kind, pa.object_type, pa.reason)

The id you pass to rt.approve(...) has to be exactly one of those ids. The most common causes when it isn't:

  • The id was already approved or denied. Approval is one-shot — each id is consumed when approve() succeeds or reject() denies it. Re-using an id after the first call always fires this error.
  • The runtime instance is fresh. Approvals don't carry across Runtime.load. If you saved a run, restarted, and tried to approve an id from before the restart, the new runtime has no record of it.
  • Typo in the id. The error message names the id you passed; the context dict also names how many approvals are currently pending, so a "currently 2 pending; none match" message is the operator signal that you have the right runtime but the wrong id.

The diligence demo's step_2_approval_demo (in examples/diligence_real_run.py) shows the canonical pattern: enumerate, then approve by id.

How to diagnose

The error message names both the offending id and the pending count:

ApprovalNotFoundError: no pending approval named 'approval_999'

What failed:
  runtime.approve('approval_999') could not find a pending approval
  with that id.
    There are currently 2 pending approvals; none of them match
    'approval_999'.

From code:

try:
    rt.approve(approval_id)
except ApprovalNotFoundError as e:
    print(e.approval_id)
    print(e.pending_count)

If pending_count is 0 and you expected approvals, either no behavior has called ctx.propose_object under a gating policy yet, or the runtime is fresh after a Runtime.load and pending approvals weren't preserved.

When does this fire

Only at runtime.approve(). The check happens twice in the implementation — once when no pack state exists (so no approvals can exist), and once after walking the pending-approvals list without finding a match. Either path produces the same error with appropriate pending_count context.

Why the framework refuses to continue

Approval ids are generated by the runtime when a behavior calls ctx.propose_object under a gating policy (memo_approval, risk_approval, etc.). Each id is unique to its runtime instance and is consumed when approve() succeeds. A miss could mean the id is stale, the runtime is fresh, or the id is a typo. The framework refuses to no-op rather than guess — silently doing nothing on an unknown id would mean an operator could approve an already-applied or non-existent action without noticing the call didn't do anything, and the audit trail would lie about what was approved.

See failure-model for the broader principle.

  • concepts/policies — the policy mechanism that produces pending approvals. Covers the ctx.propose_objectapproval.proposedruntime.approve()approval.granted lifecycle.
  • examples/diligence_real_run.py:step_2_approval_demo — the canonical enumerate-then-approve pattern, runnable.