affaan-m/ECC · error · ClaimError

required SQLite guards are disabled

Error message

required SQLite guards are disabled

What it means

The library verifies at transaction start that the PRAGMAs foreign_keys and recursive_triggers are both enabled (return 1) on the connection, because its integrity model depends on enforced foreign keys and recursive trigger behavior. If either is off it refuses to proceed, since a claim could otherwise be committed against inconsistent or orphaned rows.

Solutions

  1. Create the connection with this module's connect(path) helper, which enables both PRAGMAs
  2. Run db.execute('PRAGMA foreign_keys=ON') and db.execute('PRAGMA recursive_triggers=ON') on the connection before calling the API
  3. Check current values with PRAGMA foreign_keys; PRAGMA recursive_triggers and re-enable if 0
  4. Avoid code paths that toggle foreign_keys off on the shared connection; use a separate connection for migrations

Example fix

// before
db = sqlite3.connect('app.db')  # foreign_keys defaults to OFF
claim(db, oid, did, now=ts)  # ClaimError

// after
db = sqlite3.connect('app.db', isolation_level=None)
db.execute('PRAGMA foreign_keys=ON')
db.execute('PRAGMA recursive_triggers=ON')
claim(db, oid, did, now=ts)
Defensive patterns

Strategy: validation

Validate before calling

def ensure_sqlite_guards(db):
    for name in ('foreign_keys', 'recursive_triggers'):
        if db.execute(f'PRAGMA {name}').fetchone()[0] != 1:
            db.execute(f'PRAGMA {name}=ON')

Try / catch

try:
    token = claim(db, oid, did, now=ts)
except ClaimError as e:
    if 'required SQLite guards are disabled' in str(e):
        db.execute('PRAGMA foreign_keys=ON')
        db.execute('PRAGMA recursive_triggers=ON')
        token = claim(db, oid, did, now=ts)
    else:
        raise

Prevention

When it happens

Trigger: Using a connection created outside this module's connect() helper where foreign_keys was never enabled (the SQLite default is OFF per connection), or where something later executed PRAGMA foreign_keys=OFF (e.g. during a migration or backup routine) and never re-enabled it.

Common situations: Sharing a connection with migration code that disables foreign keys for bulk loading; creating sqlite3.connect() directly instead of via connect(); switching to another library/tool that resets PRAGMAs on the same connection.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

    """Open an existing caller-selected database; never apply schema/migrations."""
    uri = Path(path).resolve().as_uri() + '?mode=rw'
    db = sqlite3.connect(uri, uri=True, isolation_level=None, timeout=5)
    db.row_factory = sqlite3.Row
    db.execute('PRAGMA foreign_keys=ON')
    db.execute('PRAGMA recursive_triggers=ON')
    return db


@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()

View on GitHub (pinned to 8321021c54)