can1357/oh-my-pi · error · ToolError
${label} must be an integer; got '${key}'
Error message
${label} must be an integer; got '${key}' What it means
coerceIntegerKey converts a string key into a number/bigint for binding against an INTEGER primary key or rowid. If the key string is not a plain (optionally signed) decimal integer, it throws this ToolError with the given label (e.g. "Primary key 'abc'"). Very large integers fall back to BigInt, so only non-integer shapes fail.
Source
Thrown at packages/coding-agent/src/tools/sqlite-reader.ts:348
function getTableInfoRows(db: Database, table: string): SqliteTableInfoRow[] {
getTableMasterRow(db, table);
return db.prepare<SqliteTableInfoRow, []>(`PRAGMA table_info(${quoteSqliteIdentifier(table)})`).all();
}
function getTableColumns(db: Database, table: string): string[] {
return getTableInfoRows(db, table).map(column => column.name);
}
function getPrimaryKeyColumns(db: Database, table: string): SqliteTableInfoRow[] {
return getTableInfoRows(db, table)
.filter(column => column.pk > 0)
.sort((left, right) => left.pk - right.pk);
}
function coerceIntegerKey(key: string, label: string): number | bigint {
const trimmed = key.trim();
if (!/^-?\d+$/.test(trimmed)) {
throw new ToolError(`${label} must be an integer; got '${key}'`);
}
const asNumber = Number.parseInt(trimmed, 10);
if (Number.isSafeInteger(asNumber)) {
return asNumber;
}
return BigInt(trimmed);
}
function coerceLookupValue(key: string, type: string): SqliteBinding {
const normalizedType = type.trim().toUpperCase();
if (normalizedType.includes("INT")) {
return coerceIntegerKey(key, `Primary key '${key}'`);
}
if (normalizedType.includes("REAL") || normalizedType.includes("FLOA") || normalizedType.includes("DOUB")) {
const parsed = Number(key);
if (Number.isFinite(parsed)) {
return parsed;View on GitHub (pinned to 9690622007)
Solutions
- Pass a plain integer string (e.g. '42', '-7') for INT primary-key lookups, or use the rowid if the table lacks a suitable key.
- If the key is genuinely non-integer (UUID), the table's declared type likely shouldn't match INT — check PRAGMA table_info; the tool coerces based on declared type, so look up by the correct column.
- For composite primary keys, use the appropriate keyed-update path with each column bound separately rather than one joined string.
- Trim the key and validate with /^-?\d+$/ before calling.
Example fix
// before
await reader.row({ table: "users", key: "7f3c-uuid" }); // pk column is INTEGER
// after
await reader.row({ table: "users", key: "17" }); // use the integer pk/rowid Defensive patterns
Strategy: validation
Validate before calling
function assertIntegerKey(key: string): number {
const t = key.trim();
if (!/^-?\d+$/.test(t)) throw new Error(`key must be an integer: ${key}`);
return Number.parseInt(t, 10);
} Type guard
function isIntegerKey(v: string): boolean { return /^-?\d+$/.test(v.trim()); } Try / catch
try { return await reader.row({ table, key }); } catch (e) { if (e instanceof ToolError && e.message.includes("must be an integer")) { /* look up by rowid or re-fetch the correct pk */ } throw e; } Prevention
- Match key type to the declared column type — the tool coerces via PRAGMA type substring 'INT'.
- For UUID/text keys, ensure the table's pk column is actually TEXT-typed.
- Strip formatting (whitespace, prefixes) from ids before lookup.
When it happens
Trigger: Row lookup/keyed update with a non-integer key against an INT-typed primary key: UUID-style keys ('a1b2...'), composite keys ('1,2'), keys with whitespace-embedded junk, prefixed ids ('id:42'), or float strings ('1.0'). Called from coerceLookupValue, binding, and updateRowByRowId.
Common situations: Using a text primary key (UUID) against a table whose declared column type contains INT (note: SQLite type affinity matches on 'INT' substring); passing composite key parts joined into one string; copying a rowid that includes formatting.
Related errors
- SQLite limit must be a positive integer; got '${value}'
- SQLite offset must be a non-negative integer; got '${value}'
- symbol is required for project-aware ${action}; pass symbol=
- Symbol "${symbol}" occurrence ${occurrence} is out of bounds
- Report cannot be empty.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/7b4423ac98ca8b6d.
Report an issue: GitHub.