paperclipai/paperclip · error
invalid
Error message
invalid
What it means
An internal sentinel thrown inside decodeListCursor's try block when the decoded cursor payload fails semantic validation: wrong schema version, mismatched conversationId, mismatched sourceCommentId, unparseable createdAt date, or non-UUID attachmentId / sourceCommentIdTieBreak. It is immediately caught by the enclosing catch and rethrown as the public paperclip_runner_chat_attachment_cursor_invalid error, so developers never see this raw 'invalid' message — only via the rethrown code.
Solutions
- Restart pagination from page 1 (omit the cursor) — a cursor is only valid for its exact conversation and sourceCommentId.
- Verify the cursor matches the same conversationId and sourceCommentId used in the original listing request.
- Ensure client and server versions agree on the cursor schema (paperclip.chat-attachment-list-cursor.v1); upgrade stale clients.
- Send cursors back verbatim — never decode, edit, or re-encode them client-side.
Example fix
// before
const cursor = savedCursors[newConversationId]; // reused across conversations
const page = await listAttachments({ conversationId, cursor });
// after
const cursor = conversationId === savedCursorConversation ? savedCursor : undefined; // cursor is scoped to its conversation
const page = await listAttachments({ conversationId, cursor }); Defensive patterns
Strategy: validation
Validate before calling
function cursorMatchesContext(cursor: string, conversationId: string, sourceCommentId: string | null): boolean {
try {
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
return parsed.schema === "paperclip.chat-attachment-list-cursor.v1" && parsed.conversationId === conversationId;
} catch { return false; }
} Type guard
function isCursorFor(value: unknown, conversationId: string): value is string {
try {
const parsed: unknown = JSON.parse(Buffer.from(String(value), "base64url").toString("utf8"));
return typeof parsed === "object" && parsed !== null && (parsed as Record<string, unknown>).schema === "paperclip.chat-attachment-list-cursor.v1" && (parsed as Record<string, unknown>).conversationId === conversationId;
} catch { return false; }
} Try / catch
try {
page = await listAttachments({ conversationId, sourceCommentId, cursor });
} catch (err) {
if (err instanceof Error && err.message === "paperclip_runner_chat_attachment_cursor_invalid") {
page = await listAttachments({ conversationId, sourceCommentId }); // cursor/context mismatch — restart
} else {
throw err;
}
} Prevention
- Scope stored cursors by (conversationId, sourceCommentId) and discard them when either changes.
- Never reuse a cursor across conversations or after the source comment changes.
- Re-paginate from page 1 after client/server upgrades that may change cursor schema.
- Treat cursors as opaque; inspecting or editing fields guarantees this error eventually.
When it happens
Trigger: A base64url cursor that decodes to valid JSON but whose fields don't match: schema !== 'paperclip.chat-attachment-list-cursor.v1', conversationId differs from the requested conversation, sourceCommentId differs, createdAt is missing/not an ISO date, or attachmentId/sourceCommentIdTieBreak are not UUID v1-5 strings.
Common situations: Using a cursor from one conversation to list another conversation's attachments; cursors generated by a different/older schema version after an upgrade; hand-forged or mutated cursor payloads; clock/date corruption producing an invalid createdAt.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- codex_history_invalid_cursor
- paperclip_current_wake_comments_cursor_invalid
- paperclip_runner_chat_attachment_cursor_invalid
- A full lowercase source SHA is required.
- A reusable lease cannot be replaced and reacquired in the…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/498bc8791cdd33d4.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/chat-attachment-reuse.ts:257
if (typeof value !== "string" || value.length === 0 || value.length > 1024) {
throw new Error("paperclip_runner_chat_attachment_cursor_invalid");
}
try {
const parsed = record(
JSON.parse(Buffer.from(value, "base64url").toString("utf8")),
);
const createdAt =
typeof parsed.createdAt === "string" ? parsed.createdAt : "";
const parsedDate = new Date(createdAt);
if (
parsed.schema !== "paperclip.chat-attachment-list-cursor.v1" ||
parsed.conversationId !== conversationId ||
(parsed.sourceCommentId ?? null) !== sourceCommentId ||
Number.isNaN(parsedDate.getTime()) ||
!isUuid(parsed.attachmentId) ||
!isUuid(parsed.sourceCommentIdTieBreak)
) {
throw new Error("invalid");
}
return parsed as ListCursor;
} catch {
throw new Error("paperclip_runner_chat_attachment_cursor_invalid");
}
}
function destinationAllowed(
endpoint: typeof chatEndpoints.$inferSelect,
conversation: typeof chatConversations.$inferSelect,
resource: typeof chatEndpointResources.$inferSelect | null,
): boolean {
if (conversation.isDirectMessage) return endpoint.allowDirectMessages;
if (!resource || resource.availability !== "available") return false;
if (
endpoint.provider === "microsoft-teams" &&
resource.type === "group_chat"
) {View on GitHub (pinned to 3f1d897a7c)