affaan-m/ECC · error · ClaimError
now must be a nonnegative integer
Error message
now must be a nonnegative integer
What it means
All claim APIs take a now keyword used as the created/updated timestamp for claim rows, and the library requires it to be a strict int (not bool, not float, not str) that is >= 0. This guards the audit trail against malformed or misleading timestamps. Passing anything else raises ClaimError before the transaction begins.
Solutions
- Pass an integer epoch value, e.g. now=int(time.time())
- Convert floats/strings explicitly with int() before the call, verifying the value is nonnegative
- If now may be absent, default it at the call site: now = now if isinstance(now, int) and now >= 0 else int(time.time())
Example fix
// before claim(db, obligation_id, decision_id, now=time.time()) # float -> ClaimError // after claim(db, obligation_id, decision_id, now=int(time.time()))
Defensive patterns
Strategy: type-guard
Validate before calling
def valid_now(now):
return type(now) is int and now >= 0
if not valid_now(ts): raise ValueError('now must be a nonnegative int') Type guard
def is_valid_timestamp(v) -> bool:
return type(v) is int and v >= 0 # excludes bool and float Try / catch
try:
token = claim(db, oid, did, now=ts)
except ClaimError as e:
if 'now must be a nonnegative integer' in str(e):
token = claim(db, oid, did, now=int(time.time()))
else:
raise Prevention
- Always derive timestamps as int(time.time())
- Never pass floats, bools, strings, or datetime objects directly
- Centralize clock reading in one helper that returns ints
- Enable type checking (mypy) so non-int arguments are caught statically
When it happens
Trigger: claim(db, oid, did, now=None); passing now='1700000000' (string), now=time.time() (float), now=True, or now=-1 to any of claim/begin_dispatch/cancel/mark_unknown/_finish.
Common situations: Using time.time() instead of int(time.time()); passing a datetime or ISO string; None when the caller forgot to supply a clock value; JSON round-trips turning ints into floats or strings.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- approved hash must be lowercase SHA-256 hexadecimal
- rule lacks time evidence
- -32602
- a claim token is required
- a confirmed nonempty coordinate is required
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/89aaab2a3928041d.
Report an issue: GitHub.
Appendix: source
Thrown at skills/operator-approval-loop/references/approval_claims.py:36
def connect(path):
"""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:View on GitHub (pinned to 8321021c54)