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

  1. Check the claim state before cancelling; only call cancel() when state == 'claimed'.
  2. If the claim is 'dispatching', treat the outcome as uncertain: call mark_unknown() and then reconcile() with verified evidence — do not cancel.
  3. If the claim is already 'cancelled', the cancel goal is achieved; catch the ClaimError and treat it as an idempotent no-op.
  4. 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

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


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)