affaan-m/ECC · error · ClaimError
only a pre-dispatch claim can be cancelled
Error message
only a pre-dispatch claim can be cancelled
What it means
cancel() raises this ClaimError when the claim is no longer in the pre-dispatch 'claimed' state. Cancellation is only meaningful before a dispatch has been granted; once the claim is 'dispatching', 'delivered', 'unknown', or 'cancelled', the operation is refused so an in-flight or finished delivery can never be retroactively undone.
Solutions
- Check the claim state before cancelling; only call cancel() when state == 'claimed'.
- If the claim is 'dispatching', treat the outcome as uncertain: call mark_unknown() and then reconcile() with verified evidence — do not cancel.
- If the claim is already 'cancelled', the cancel goal is achieved; catch the ClaimError and treat it as an idempotent no-op.
- If it is 'delivered', the delivery is final; use whatever business-level reversal exists outside this module.
Example fix
// before
def safe_cancel(db, token, now):
claims.cancel(db, token, now=now) # raises if worker already dispatched
// after
def safe_cancel(db, token, now):
row = db.execute("SELECT state FROM obligation_delivery_claims WHERE token=?", (token,)).fetchone()
if row and row['state'] == 'claimed':
claims.cancel(db, token, now=now)
# 'cancelled' -> already done; anything else -> cannot cancel Defensive patterns
Strategy: validation
Validate before calling
row = db.execute("SELECT state FROM obligation_delivery_claims WHERE token=?", (token,)).fetchone()
if row is None or row['state'] != 'claimed':
return False # nothing to cancel / not cancellable Try / catch
try:
claims.cancel(db, token, now=ts)
except claims.ClaimError:
log.info("claim %s no longer pre-dispatch; cancel skipped", token) Prevention
- Check claim state immediately before cancelling
- Make cancel idempotent in caller code: treat already-cancelled as success
- Coordinate cancellation UI with the dispatch worker to avoid racing
When it happens
Trigger: Calling cancel(db, token) on a token that is already 'dispatching' (begin_dispatch succeeded), 'delivered' (complete/reconcile ran), 'cancelled' (double cancel), or 'unknown' (mark_unknown ran).
Common situations: A background worker already picked up the claim while an admin UI tried to cancel it; a job scheduler re-running a cancel step after a first successful cancel; trying to back out a delivery after reconciliation.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- claim cannot grant another dispatch
- completion requires the correct dispatch/reconciliation…
- only a dispatched attempt can become unknown
- an active harness configuration already exists
- baseline is not the active harness configuration
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/ebfc83a57d14efd5.
Report an issue: GitHub.
Appendix: source
Thrown at skills/operator-approval-loop/references/approval_claims.py:112
"""
with _transaction(db, now):
row = _claim_row(db, token)
if row['state'] != 'claimed':
raise ClaimError('claim cannot grant another dispatch')
payload = _snapshot(db, row['obligation_id'], row['decision_id'])
changed = db.execute('''UPDATE obligation_delivery_claims SET state='dispatching',updated_ts=?
WHERE token=? AND state='claimed' ''', (now, token)).rowcount
if changed != 1:
raise ClaimError('dispatch transition lost')
return payload
def cancel(db, token, *, now):
"""Cancel only a not-yet-dispatched claim. Never reopen its decision key."""
with _transaction(db, now):
row = _claim_row(db, token)
if row['state'] != 'claimed':
raise ClaimError('only a pre-dispatch claim can be cancelled')
db.execute("UPDATE obligation_delivery_claims SET state='cancelled',updated_ts=? WHERE token=?",
(now, token))
def mark_unknown(db, token, *, now):
"""Record uncertainty, including a restarted worker's dispatching claim."""
with _transaction(db, now):
row = _claim_row(db, token)
if row['state'] == 'unknown':
return
if row['state'] != 'dispatching':
raise ClaimError('only a dispatched attempt can become unknown')
db.execute("UPDATE obligation_delivery_claims SET state='unknown',updated_ts=? WHERE token=?",
(now, token))
def _finish(db, token, coordinate, now, evidence):
if not isinstance(coordinate, str) or not coordinate.strip():View on GitHub (pinned to 8321021c54)