can1357/oh-my-pi · error · ToolError

SQLite write content must be a JSON object

Error message

SQLite write content must be a JSON object

What it means

The WriteTool's SQLite branch (path like sqlite://...) parses the `content` argument as JSON5 and then requires the parsed result to be a plain object. A JSON5 scalar (number, string, boolean, null) or array is rejected because the writer needs key/value pairs to map onto table columns or a JSON document. It is thrown as a ToolError so the agent sees it as a recoverable tool failure.

Source

Thrown at packages/coding-agent/src/tools/write.ts:797

					lookup.kind === "pk"
						? deleteRowByKey(db, resolvedSqlitePath.table, lookup, resolvedSqlitePath.key)
						: deleteRowByRowId(db, resolvedSqlitePath.table, resolvedSqlitePath.key);
				resultText =
					deleted > 0
						? `Deleted row '${resolvedSqlitePath.key}' from ${resolvedSqlitePath.table}`
						: `No row deleted from ${resolvedSqlitePath.table} for key '${resolvedSqlitePath.key}'`;
			} else {
				let parsedContent: unknown;
				try {
					parsedContent = Bun.JSON5.parse(content);
				} catch (error) {
					throw new ToolError(
						`SQLite write content must be valid JSON5: ${error instanceof Error ? error.message : String(error)}`,
					);
				}

				if (!isRecord(parsedContent)) {
					throw new ToolError("SQLite write content must be a JSON object");
				}

				if (resolvedSqlitePath.key) {
					const lookup = resolveTableRowLookup(db, resolvedSqlitePath.table);
					const updated =
						lookup.kind === "pk"
							? updateRowByKey(db, resolvedSqlitePath.table, lookup, resolvedSqlitePath.key, parsedContent)
							: updateRowByRowId(db, resolvedSqlitePath.table, resolvedSqlitePath.key, parsedContent);
					resultText =
						updated > 0
							? `Updated row '${resolvedSqlitePath.key}' in ${resolvedSqlitePath.table}`
							: `No row updated in ${resolvedSqlitePath.table} for key '${resolvedSqlitePath.key}'`;
				} else {
					insertRow(db, resolvedSqlitePath.table, parsedContent);
					resultText = `Inserted row into ${resolvedSqlitePath.table}`;
				}
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Wrap the payload in an object, e.g. `{"rows":[1,2,3]}` or a single record object keyed by column names.
  2. If a multi-row insert is intended, pass `{"table":"t","rows":[...]}` or the format documented for the sqlite:// path.
  3. Validate the JSON5 parses to an object before calling the tool.

Example fix

// before
content: "[1, 2, 3]"
// after
content: "{ rows: [1, 2, 3] }"
Defensive patterns

Strategy: validation

Validate before calling

const parsed = Bun.JSON5.parse(content);
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
  throw new Error("SQLite write content must be a JSON object");
}

Type guard

function isRecord(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Prevention

When it happens

Trigger: Calling the write tool with a sqlite:// target where `content` parses successfully as JSON5 but is not an object — e.g. content="[1,2,3]", content="42", or content="'just a string'".

Common situations: Models pass array payloads intending multi-row inserts, quote-escaped plain strings, or null/numeric literals when writing to a JSON document column; also hand-crafted RPC/SDK calls to the write tool that skip object normalization.

Related errors


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