{"record":{"id":"6524317e8115a3fc","repo":"earendil-works/pi","slug":"invalid-query","errorCode":"invalid_query","errorMessage":"limit must be a positive integer","messagePattern":"limit must be a positive integer","errorType":"validation","errorClass":"SessionError","httpStatus":null,"severity":"error","filePath":"packages/agent/src/harness/session/session.ts","lineNumber":32,"sourceCode":"\tProvisionedEntry,\n\tRecordBase,\n\tRecordQuery,\n\tSessionMetadata,\n\tSessionStats,\n\tSessionStorage,\n\tSessionTree,\n} from \"./types.ts\";\nimport { SessionError } from \"./types.ts\";\n\ntype JsonValidationFrame = { value: unknown } | { exit: object };\n\nfunction invalidPayload(reason: string): never {\n\tthrow new SessionError(\"invalid_payload\", `Durable payload ${reason}`);\n}\n\nfunction assertValidLimit(limit: number | undefined): void {\n\tif (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {\n\t\tthrow new SessionError(\"invalid_query\", \"limit must be a positive integer\");\n\t}\n}\n\nfunction assertValidCursor(afterSeq: number | undefined): void {\n\tif (afterSeq !== undefined && (!Number.isInteger(afterSeq) || afterSeq < 0)) {\n\t\tthrow new SessionError(\"invalid_query\", \"cursor sequence must be a non-negative integer\");\n\t}\n}\n\nexport function assertJsonSerializable(value: unknown): void {\n\tconst active = new WeakSet<object>();\n\tconst stack: JsonValidationFrame[] = [{ value }];\n\twhile (stack.length > 0) {\n\t\tconst frame = stack.pop()!;\n\t\tif (\"exit\" in frame) {\n\t\t\tactive.delete(frame.exit);\n\t\t\tcontinue;\n\t\t}","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/earendil-works/pi/blob/4af9d21d3b4d664e4a29fcabfec85171077248e3/packages/agent/src/harness/session/session.ts#L14-L50","documentation":"All Session query APIs — findEntries, findEntriesOnBranch, findRecords, getLog, findOpenOperations — validate their limit option up front. limit must be an integer strictly greater than zero; undefined means unlimited. This is the facade copy in session.ts; the identical storage-layer copy lives at state.ts:32, so backends built on SessionState get the same guard.","triggerScenarios":"findEntries({ limit: 0 }) where 0 was meant as 'no limit' (undefined is the way to express that); limit: -1; fractional limits such as 10.5; NaN produced by Number('') or unvalidated user input; computed limits like arr.length - 1 on an empty array.","commonSituations":"Pagination code that encodes 'return everything' as 0; page sizes parsed from CLI args, env vars, or config strings without range checks; float-producing page-size math; a typoed default limit in a config file.","solutions":["Omit limit (or pass undefined) when you want all results — 0 is not 'unlimited'","Sanitize before querying: const safe = Number.isInteger(limit) && limit > 0 ? limit : undefined","Parse user-supplied limits with Number.parseInt and range-check them before they reach a query","Use findEntry/findEntryOnBranch for single results instead of limit arithmetic"],"exampleFix":"// before\nconst entries = await session.findEntries({ limit: 0 });\n// after\nconst limit = Number.isInteger(n) && n > 0 ? n : undefined;\nconst entries = await session.findEntries({ limit });","handlingStrategy":"validation","validationCode":"const normalizeLimit = (limit: number | undefined): number | undefined =>\n  limit !== undefined && Number.isInteger(limit) && limit > 0 ? limit : undefined;\n\nawait session.findEntries({ limit: normalizeLimit(userLimit) });","typeGuard":"const isValidLimit = (limit: unknown): limit is number =>\n  typeof limit === 'number' && Number.isInteger(limit) && limit > 0;","tryCatchPattern":"try {\n  await session.getLog({ limit: parsed });\n} catch (error) {\n  if (error instanceof SessionError && error.code === 'invalid_query' && error.message.startsWith('limit')) {\n    await session.getLog({}); // safe fallback: unlimited\n  } else {\n    throw error;\n  }\n}","preventionTips":["Build all query objects through one pagination helper that validates limit","Use undefined, never 0, to mean 'no limit'","Validate config- and CLI-derived limits at startup, not at query time"],"tags":["query","pagination","validation","session"],"backgroundTag":"invalid-pagination-limit","analyzedSha":"4af9d21d3b4d664e4a29fcabfec85171077248e3","analyzedAt":"2026-08-24T13:07:14.692Z","schemaVersion":2},"datasetVersion":"2026-08-24T17:17:21.512Z"}