can1357/oh-my-pi · error · ToolError

SQLite raw queries do not support bound parameters

Error message

SQLite raw queries do not support bound parameters

What it means

executeReadQuery prepares a raw `q=` SQL query typed with zero bound parameters and rejects any statement whose paramsCount > 0. The selector API is purely read-only and string-based (a URL query param), so it cannot accept separate binding values; instead of misbinding or enabling injection games, it throws.

Source

Thrown at packages/coding-agent/src/tools/sqlite-reader.ts:769

	const binding = coerceLookupValue(key, pk.type ?? "");
	return db.prepare<SqliteRow, SQLQueryBindings[]>(sql).get(binding);
}

export function getRowByRowId(db: Database, table: string, key: string): Record<string, unknown> | null {
	getTableMasterRow(db, table);
	const binding = coerceIntegerKey(key, "SQLite ROWID");
	return db
		.prepare<SqliteRow, SQLQueryBindings[]>(`SELECT * FROM ${quoteSqliteIdentifier(table)} WHERE rowid = ? LIMIT 1`)
		.get(binding);
}

export function executeReadQuery(
	db: Database,
	sql: string,
): { columns: string[]; rows: Record<string, unknown>[]; truncated: boolean } {
	const statement = db.prepare<SqliteRow, []>(sql);
	if (statement.paramsCount > 0) {
		throw new ToolError("SQLite raw queries do not support bound parameters");
	}
	const columns = [...statement.columnNames];
	const rows: SqliteRow[] = [];
	let truncated = false;
	for (const row of statement.iterate()) {
		if (rows.length >= MAX_RAW_QUERY_ROWS) {
			truncated = true;
			break;
		}
		rows.push(row);
	}
	return { columns, rows, truncated };
}

export function insertRow(db: Database, table: string, data: Record<string, unknown>): void {
	getTableMasterRow(db, table);
	const entries = validateWriteColumns(db, table, data);
	if (entries.length === 0) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Inline the literal values directly into the SQL: `q=SELECT * FROM t WHERE id = 42`
  2. Ensure string literals are properly quoted: `q=SELECT * FROM t WHERE name = 'foo'`
  3. If parameterization is required, execute the query outside this tool with a real SQLite client (bun:sqlite) and bind values there

Example fix

// before
sqlite://app.db?q=SELECT * FROM users WHERE id = ?
// after
sqlite://app.db?q=SELECT * FROM users WHERE id = 42
Defensive patterns

Strategy: validation

Validate before calling

// before building the q= selector, inline all values
if (/\?|:[a-zA-Z_]+|@[a-zA-Z_]+|\$[a-zA-Z_]+/.test(sql)) {
  throw new Error("Raw q= queries cannot contain bind placeholders; inline literal values");
}

Try / catch

try {
  const result = executeReadQuery(db, sql);
} catch (err) {
  if (err instanceof ToolError && err.message.includes("bound parameters")) {
    // inline the values into sql and retry
  } else throw err;
}

Prevention

When it happens

Trigger: Running `db.sqlite?q=SELECT * FROM t WHERE id = ?` (or `?1`, `:name`, `@name` placeholders) through the sqlite reader selector.

Common situations: Copying a prepared statement from application code into the q= selector; assuming the tool supports parameterized queries; templating tools that emit `?` placeholders.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/af8681788149d2f7. Report an issue: GitHub.