can1357/oh-my-pi · error · ToolError
error instanceof Error ? error.message : String(error) (wrap
Error message
error instanceof Error ? error.message : String(error) (wraps underlying error)
What it means
readSqlite wraps any non-ToolError thrown inside the read (SQLite driver errors, malformed DB files, SQL failures in raw queries) into a ToolError whose message is error.message (or String(error)). The original stack/type is discarded, so callers only see the flattened message string.
Source
Thrown at packages/coding-agent/src/tools/read-sqlite.ts:211
table: "query",
dbPath: resolvedSqlitePath.absolutePath,
});
if (result.truncated) {
output += `\n[Output capped at ${MAX_RAW_QUERY_ROWS} rows; add a LIMIT/OFFSET clause to the query to page through more]`;
}
return toolResult<ReadToolDetails>(details)
.text(prependSuffixResolutionNotice(output, resolvedSqlitePath.suffixResolution))
.sourcePath(resolvedSqlitePath.absolutePath)
.done();
}
}
throw new ToolError("Unsupported SQLite selector");
} catch (error) {
if (error instanceof ToolError) {
throw error;
}
throw new ToolError(error instanceof Error ? error.message : String(error));
} finally {
db?.close();
}
}
View on GitHub (pinned to 9690622007)
Solutions
- Read the wrapped message — it usually names the SQLite failure (e.g. 'file is not a database', 'no such table').
- Verify the file is a real SQLite DB (magic header 'SQLite format 3\u0000').
- Fix the SQL in :raw selectors (check table/column names via :schema first).
- Close other connections holding a write lock; retry if it was transient 'database is locked'.
- If the stack trace matters, run outside the tool wrapper (e.g. sqlite3 CLI) to see full details.
Example fix
// before: raw unvetted SQL file.db:raw?sql=SELEC * FROM users // after: validated SQL and correct names file.db:raw?sql=SELECT * FROM users LIMIT 20
Defensive patterns
Strategy: try-catch
Validate before calling
import { $ } from 'bun';
// verify it is a SQLite database before reading
const header = Buffer.from(await Bun.file(dbPath).slice(0, 16).arrayBuffer());
if (!header.toString('latin1').startsWith('SQLite format 3')) {
throw new Error(`${dbPath} is not a SQLite database`);
} Type guard
function isSqliteFile(header: Uint8Array): boolean {
return Array.from(header.slice(0, 16)).join(',') ===
Array.from(Buffer.from('SQLite format 3\u0000', 'latin1')).join(',');
} Try / catch
try {
return await readSqlite(dbPath, selector);
} catch (e) {
if (e instanceof ToolError) {
// message is the flattened sqlite driver error
if (/file is not a database/i.test(e.message)) { /* wrong file type */ }
if (/database is locked/i.test(e.message)) { /* retry after releasing writer */ }
}
throw e;
} Prevention
- Check the 16-byte SQLite magic header before opening unknown files.
- Run read-only queries; keep other writers from holding locks during reads.
- Validate :raw SQL table/column names via the :schema selector first.
- Decrypt/convert SQLCipher or WAL-orphaned files before tool reads.
When it happens
Trigger: executeReadQuery on invalid SQL in a :raw selector; queryRows/getTableSchema hitting a corrupt or encrypted (non-SQLite) file; native sqlite driver throwing (disk I/O, locked DB); any bug in helper functions inside the try block.
Common situations: Passing a text file, WAL-only remnants, or an encrypted SQLCipher database where SQLite header magic fails; writing SELECT with a typo in :raw mode; database locked by another writer.
Related errors
- transparent (brush_core::Error)
- Failed to open auth database at '${dbPath}' after ${maxAttem
- Persistent credential block store ${store} is unavailable af
- WAL checkpoint failed for ${dbPath}: busy=${result.busy}, wa
- lines.join("\n")
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/fc3f3afb8c4de664.
Report an issue: GitHub.