{"record":{"id":"e0a2087c55da8dfb","repo":"can1357/oh-my-pi","slug":"sqlite-offset-must-be-a-non-negative-integer-got","errorCode":null,"errorMessage":"SQLite offset must be a non-negative integer; got '${value}'","messagePattern":"SQLite offset must be a non-negative integer; got '(.+?)'","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/sqlite-reader.ts","lineNumber":312,"sourceCode":"\tif (value === null || value.trim().length === 0) {\n\t\treturn fallback;\n\t}\n\n\tconst parsed = Number.parseInt(value, 10);\n\tif (!Number.isFinite(parsed) || parsed < 1) {\n\t\tthrow new ToolError(`SQLite limit must be a positive integer; got '${value}'`);\n\t}\n\treturn Math.min(parsed, MAX_QUERY_LIMIT);\n}\n\nfunction parseOffset(value: string | null): number {\n\tif (value === null || value.trim().length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst parsed = Number.parseInt(value, 10);\n\tif (!Number.isFinite(parsed) || parsed < 0) {\n\t\tthrow new ToolError(`SQLite offset must be a non-negative integer; got '${value}'`);\n\t}\n\treturn parsed;\n}\n\nfunction getTableMasterRow(db: Database, table: string): SqliteMasterRow {\n\tconst row =\n\t\tdb\n\t\t\t.prepare<SqliteMasterRow, [string]>(\n\t\t\t\t\"SELECT name, sql FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name = ?\",\n\t\t\t)\n\t\t\t.get(table) ?? null;\n\tif (!row) {\n\t\tthrow new ToolError(`SQLite table '${table}' not found`);\n\t}\n\treturn row;\n}\n\nfunction getTableInfoRows(db: Database, table: string): SqliteTableInfoRow[] {","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/sqlite-reader.ts#L294-L330","documentation":"parseOffset validates the `offset` query parameter of SQLite selector URLs. It throws this ToolError when offset is present, non-empty, and not a parseable integer >= 0 (e.g. '-1', 'ten', '1.5'). Empty/null offset defaults to 0.","triggerScenarios":"A selector with offset=-1, a float like '2.5', or non-numeric text such as 'end'; arithmetic producing NaN stringified into the URL (e.g. `${page * size}` with undefined page).","commonSituations":"Pagination math with undefined page variables; negative page indexes from zero-based pagination wrap-around; hand-written offsets with typos.","solutions":["Pass a non-negative integer offset (0 or greater), or omit it to start at 0.","Guard pagination math: Math.max(0, (page - 1) * size) with Number.isFinite checks.","Validate numeric query params before interpolating them into the selector string."],"exampleFix":"// before\nconst url = `db.sqlite?table=logs&offset=${(page - 1) * size}`; // page undefined -> NaN\n// after\nconst offset = Math.max(0, ((Number(page) || 1) - 1) * size);\nconst url = `db.sqlite?table=logs&offset=${offset}`;","handlingStrategy":"validation","validationCode":"function safeOffset(v: string | null | undefined): string {\n  if (v == null || v.trim() === \"\") return \"0\";\n  const n = Number.parseInt(v, 10);\n  if (!Number.isInteger(n) || n < 0) throw new Error(`bad offset: ${v}`);\n  return String(n);\n}","typeGuard":"function isNonNegativeIntString(v: string): boolean { return /^\\d+$/.test(v.trim()); }","tryCatchPattern":"try { return await readSelector(url); } catch (e) { if (e instanceof ToolError && e.message.startsWith(\"SQLite offset must be\")) { return readSelector(setQueryParam(url, \"offset\", \"0\")); } throw e; }","preventionTips":["Guard pagination math with Math.max(0, ...) and Number.isFinite checks.","Never let undefined/NaN flow into offset via template literals.","Omit the offset param entirely when starting from the first page."],"tags":["validation","parameters","sqlite"],"backgroundTag":"invalid-parameter-value","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}