earendil-works/pi · error · SessionError

invalid_query

invalid_query

Error message

limit must be a positive integer

What it means

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.

Source

Thrown at packages/agent/src/harness/session/session.ts:32

	ProvisionedEntry,
	RecordBase,
	RecordQuery,
	SessionMetadata,
	SessionStats,
	SessionStorage,
	SessionTree,
} from "./types.ts";
import { SessionError } from "./types.ts";

type JsonValidationFrame = { value: unknown } | { exit: object };

function invalidPayload(reason: string): never {
	throw new SessionError("invalid_payload", `Durable payload ${reason}`);
}

function assertValidLimit(limit: number | undefined): void {
	if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {
		throw new SessionError("invalid_query", "limit must be a positive integer");
	}
}

function assertValidCursor(afterSeq: number | undefined): void {
	if (afterSeq !== undefined && (!Number.isInteger(afterSeq) || afterSeq < 0)) {
		throw new SessionError("invalid_query", "cursor sequence must be a non-negative integer");
	}
}

export function assertJsonSerializable(value: unknown): void {
	const active = new WeakSet<object>();
	const stack: JsonValidationFrame[] = [{ value }];
	while (stack.length > 0) {
		const frame = stack.pop()!;
		if ("exit" in frame) {
			active.delete(frame.exit);
			continue;
		}

View on GitHub (pinned to 4af9d21d3b)

Solutions

  1. Omit limit (or pass undefined) when you want all results — 0 is not 'unlimited'
  2. Sanitize before querying: const safe = Number.isInteger(limit) && limit > 0 ? limit : undefined
  3. Parse user-supplied limits with Number.parseInt and range-check them before they reach a query
  4. Use findEntry/findEntryOnBranch for single results instead of limit arithmetic

Example fix

// before
const entries = await session.findEntries({ limit: 0 });
// after
const limit = Number.isInteger(n) && n > 0 ? n : undefined;
const entries = await session.findEntries({ limit });
Defensive patterns

Strategy: validation

Validate before calling

const normalizeLimit = (limit: number | undefined): number | undefined =>
  limit !== undefined && Number.isInteger(limit) && limit > 0 ? limit : undefined;

await session.findEntries({ limit: normalizeLimit(userLimit) });

Type guard

const isValidLimit = (limit: unknown): limit is number =>
  typeof limit === 'number' && Number.isInteger(limit) && limit > 0;

Try / catch

try {
  await session.getLog({ limit: parsed });
} catch (error) {
  if (error instanceof SessionError && error.code === 'invalid_query' && error.message.startsWith('limit')) {
    await session.getLog({}); // safe fallback: unlimited
  } else {
    throw error;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24). Data as JSON: /api/errors/6524317e8115a3fc. Report an issue: GitHub.