slopus/happy · error
Invalid cursor format
Error message
Invalid cursor format
What it means
feedGet() paginates user feed items by a cursor that must have the form '0-<counter>' (a Storm/MySQL-style log sequence number prefix). A 'before' cursor that does not start with '0-' is rejected with 'Invalid cursor format' because the counter cannot be parsed for a lt comparison.
Source
Thrown at packages/happy-server/sources/app/feed/feedGet.ts:26
* Returns items in reverse chronological order (newest first).
* Supports cursor-based pagination using the counter field.
*/
export async function feedGet(
tx: Tx,
ctx: Context,
options?: FeedOptions
): Promise<FeedResult> {
const limit = options?.limit ?? 100;
const cursor = options?.cursor;
// Build where clause for cursor pagination
const where: Prisma.UserFeedItemWhereInput = { userId: ctx.uid };
if (cursor?.before !== undefined) {
if (cursor.before.startsWith('0-')) {
where.counter = { lt: parseInt(cursor.before.substring(2), 10) };
} else {
throw new Error('Invalid cursor format');
}
} else if (cursor?.after !== undefined) {
if (cursor.after.startsWith('0-')) {
where.counter = { gt: parseInt(cursor.after.substring(2), 10) };
} else {
throw new Error('Invalid cursor format');
}
}
// Fetch items + 1 to determine hasMore
const items = await tx.userFeedItem.findMany({
where,
orderBy: { counter: 'desc' },
take: limit + 1
});
// Check if there are more items
const hasMore = items.length > limit;View on GitHub (pinned to b824cd0a46)
Solutions
- Use cursors exactly as returned by the previous page (the raw counter-based string, e.g. '0-42').
- Fix the client to build cursors as `0-${counter}` from the last item's counter field.
- Clear stale persisted pagination state and restart pagination from the beginning (omit cursor).
- Add client-side validation: cursor must match /^0-\d+$/ before sending.
Example fix
// before
fetchFeed({ before: lastItem.id });
// after
fetchFeed({ before: `0-${lastItem.counter}` }); Defensive patterns
Strategy: validation
Validate before calling
function isValidCursor(c) {
return c === undefined || c.before === undefined || /^0-\d+$/.test(c.before);
}
if (!isValidCursor(cursor)) throw new Error('before cursor must be "0-<counter>"'); Type guard
function isBeforeCursor(c) {
return typeof c === 'object' && c !== null &&
typeof c.before === 'string' && /^0-\d+$/.test(c.before);
} Try / catch
try {
return await api.feed.items({ cursor });
} catch (e) {
if (e.message === 'Invalid cursor format') {
resetPagination();
return await api.feed.items({});
}
throw e;
} Prevention
- Store cursors exactly as returned by the API; never hand-construct them.
- Version cursor format in persisted client state and reset on mismatch.
- Build cursors only from the item's counter field: `0-${counter}`.
- Add client-side regex validation before sending pagination requests.
When it happens
Trigger: Calling the feed items query with cursor.before set to a string not starting with '0-', e.g. 'abc', '1-100', an opaque id, or an empty-but-defined string.
Common situations: Clients caching cursors from a previous backend version that used a different format; hand-constructing cursors from item ids instead of the counter; passing a base64/encoded cursor where a plain '0-N' string is expected.
Related errors
- Usage: happy acp <agent-name> or happy acp -- <command> [arg
- Missing command after "--". Usage: happy acp -- <command> [a
- Authentication failed
- Server unavailable
- Unsupported Claude goal action
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/89823232edd27021.
Report an issue: GitHub.