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
- Inspect error.__cause__ (the original sqlite3.Error) to identify the underlying SQLite failure
- For SQLITE_BUSY/locked errors, serialize writers or retry with backoff after checking no claim was created
- Fix constraint violations (e.g. duplicate obligation_id in approval_bound_drafts) in your data before retrying
- 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
- Keep SQLite writer contention low; serialize claim writes across workers
- Set a generous busy timeout and retry SQLITE_BUSY with exponential backoff
- Fix constraint conflicts in data before retrying the same claim
- Always read __cause__ to see the real SQLite error
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
- a top-level committed transaction is required
- a current bound approved draft is required
- approved hash must be lowercase SHA-256 hexadecimal
- approved text must be valid UTF-8 text
- candidate alias collision with different immutable target
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)