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:
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 orreject()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.
What's related¶
concepts/policies— the policy mechanism that produces pending approvals. Covers thectx.propose_object→approval.proposed→runtime.approve()→approval.grantedlifecycle.examples/diligence_real_run.py:step_2_approval_demo— the canonical enumerate-then-approve pattern, runnable.