affaan-m/ECC · error · ClaimError
approved text must be valid UTF-8 text
Error message
approved text must be valid UTF-8 text
What it means
The stored draft_text in the approval snapshot must be valid UTF-8-encodable text; the library hashes it as UTF-8 bytes to compare against the stored SHA-256. If the value is not a string (AttributeError on .encode) or contains unencodable content (UnicodeError), it raises this error rather than proceeding with an unverifiable snapshot.
Solutions
- Ensure the trusted writer stores draft_text as a Python str (TEXT column)
- Decode/re-encode the stored value correctly before the approval flow writes it: text = blob.decode('utf-8')
- Inspect the offending row's typeof(draft_text) in SQLite to confirm it is 'text', not 'blob' or 'null'
- Re-record the snapshot through the trusted decision writer after fixing the storage layer
Example fix
// before
db.execute('INSERT INTO approval_bound_drafts ... VALUES (?,?)', (oid, did, draft_bytes)) # BLOB
// after
db.execute('INSERT INTO approval_bound_drafts ... VALUES (?,?)', (oid, did, draft_bytes.decode('utf-8'))) Defensive patterns
Strategy: validation
Validate before calling
def draft_text_ok(db, oid, did) -> bool:
row = db.execute('SELECT draft_text FROM approval_bound_drafts WHERE obligation_id=? AND decision_id=?',
(oid, did)).fetchone()
if not isinstance(row['draft_text'], str):
return False
try:
row['draft_text'].encode('utf-8')
return True
except UnicodeError:
return False Type guard
def is_utf8_text(v) -> bool:
return isinstance(v, str) and (lambda: (v.encode('utf-8'), True)[1])() if not any(ord(c) >= 0xD800 and ord(c) <= 0xDFFF for c in v) else False Try / catch
try:
token = claim(db, oid, did, now=ts)
except ClaimError as e:
if 'valid UTF-8 text' in str(e):
re_record_snapshot_via_trusted_writer(oid, did)
else:
raise Prevention
- Store draft_text as TEXT via a Python str; never bytes or None
- Sanitize text to remove lone surrogates before writing (text.encode('utf-8', errors='strict'))
- Check typeof(draft_text)='text' in a data-quality audit
- Pin the schema so draft_text cannot drift to BLOB
When it happens
Trigger: draft_text stored as bytes, None, or another non-str type in approval_bound_drafts; a surrogate-containing or otherwise invalid text value that fails .encode('utf-8').
Common situations: Writing the draft via raw sqlite3 with a BLOB column or bytes value; a different writer/tool storing decoded-with-errors text containing lone surrogates; schema drift where draft_text's declared type changed.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- must contain valid UTF-8 text.
- a current bound approved draft is required
- a top-level committed transaction is required
- approved hash must be lowercase SHA-256 hexadecimal
- candidate alias collision with different immutable target
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/6ec81761853dc2e3.
Report an issue: GitHub.
Appendix: source
Thrown at skills/operator-approval-loop/references/approval_claims.py:59
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')
return dict(row)
def _claim_row(db, token):
if not isinstance(token, str) or not token:
raise ClaimError('a claim token is required')
row = db.execute('SELECT * FROM obligation_delivery_claims WHERE token=?', (token,)).fetchone()
if row is None:
raise ClaimError('unknown claim token')
return row
View on GitHub (pinned to 8321021c54)