affaan-m/ECC · error · ClaimError

claim transaction failed; no permission granted

Error message

claim transaction failed; no permission granted

What it means

When a sqlite3.Error (any SQLite-level failure such as a locked database, constraint violation, or I/O error) escapes the guarded transaction, the library rolls everything back and re-raises it as this generic ClaimError, chaining the original. The message emphasizes that no dispatch permission was granted, so the caller must not treat the operation as partially done.

Solutions

  1. Inspect error.__cause__ (the original sqlite3.Error) to identify the underlying SQLite failure
  2. For SQLITE_BUSY/locked errors, serialize writers or retry with backoff after checking no claim was created
  3. Fix constraint violations (e.g. duplicate obligation_id in approval_bound_drafts) in your data before retrying
  4. Ensure the database file is writable and the schema matches what the library expects

Example fix

// before
try:
    token = claim(db, oid, did, now=ts)
except sqlite3.OperationalError:  # never raised; wrapped
    ...

// after
try:
    token = claim(db, oid, did, now=ts)
except ClaimError as e:
    cause = e.__cause__
    if isinstance(cause, sqlite3.OperationalError) and 'locked' in str(cause):
        retry_with_backoff()
    else:
        raise
Defensive patterns

Strategy: try-catch

Validate before calling

# Pre-flight: ensure DB is reachable and writable
row = db.execute('SELECT 1').fetchone()  # raises sqlite3.Error early if broken

Try / catch

try:
    token = claim(db, oid, did, now=ts)
except ClaimError as e:
    cause = e.__cause__
    if isinstance(cause, sqlite3.OperationalError) and 'locked' in str(cause):
        schedule_retry_with_backoff()
    elif isinstance(cause, sqlite3.IntegrityError):
        log_duplicate_or_fk_issue(oid, did)
    else:
        raise
# On any ClaimError here, treat NO permission as granted.

Prevention

When it happens

Trigger: BEGIN IMMEDIATE hitting SQLITE_BUSY after the 5s timeout (another writer holds the DB); an INSERT/UPDATE inside claim() violating a UNIQUE or FOREIGN KEY constraint; disk I/O or database-corruption errors during the transaction.

Common situations: Concurrent workers contending for the same SQLite file; attempting claim() twice for the same obligation hitting a uniqueness constraint; read-only filesystem or missing database file in mode=rw.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/c7e3be456e6878a4. Report an issue: GitHub.

Appendix: source

Thrown at skills/operator-approval-loop/references/approval_claims.py:47

@contextmanager
def _transaction(db, now):
    # Never return permission whose commit belongs to an outer caller transaction.
    if db.in_transaction:
        raise ClaimError('a top-level committed transaction is required')
    if type(now) is not int or now < 0:
        raise ClaimError('now must be a nonnegative integer')
    if any(db.execute(f'PRAGMA {name}').fetchone()[0] != 1
           for name in ('foreign_keys', 'recursive_triggers')):
        raise ClaimError('required SQLite guards are disabled')
    try:
        db.execute('BEGIN IMMEDIATE')
        yield
        db.commit()
    except BaseException as error:
        db.rollback()
        if isinstance(error, sqlite3.Error):
            raise ClaimError('claim transaction failed; no permission granted') from error
        raise


def _snapshot(db, obligation_id, decision_id):
    row = db.execute('''SELECT * FROM approval_bound_drafts
        WHERE obligation_id=? AND decision_id=?''', (obligation_id, decision_id)).fetchone()
    if row is None:
        raise ClaimError('a current bound approved draft is required')
    try:
        digest = hashlib.sha256(row['draft_text'].encode('utf-8')).hexdigest()
    except (AttributeError, UnicodeError) as error:
        raise ClaimError('approved text must be valid UTF-8 text') from error
    stored_digest = row['draft_sha256']
    if (not isinstance(stored_digest, str) or len(stored_digest) != 64
            or any(character not in '0123456789abcdef' for character in stored_digest)):
        raise ClaimError('approved hash must be lowercase SHA-256 hexadecimal')
    if not secrets.compare_digest(digest, stored_digest):
        raise ClaimError('approved text hash does not match')

View on GitHub (pinned to 8321021c54)