{"record":{"id":"2cfdf44ce4821c8b","repo":"mastra-ai/mastra","slug":"linear-cursor-is-invalid","errorCode":null,"errorMessage":"Linear cursor is invalid.","messagePattern":"Linear cursor is invalid\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/integrations/platform/linear/integration.ts","lineNumber":671,"sourceCode":"    return { workspaceId: parsed.workspaceId, projectId: parsed.projectId };\n  } catch {\n    throw new Error('Linear project source id is invalid.');\n  }\n}\n\nfunction normalizeLabels(labels: string[] | undefined): string[] {\n  return [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n}\n\nfunction decodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null | undefined> {\n  if (!cursor) return {};\n  if (sourceIds.length === 1) return { [sourceIds[0]!]: cursor };\n  try {\n    const parsed = JSON.parse(cursor) as unknown;\n    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error();\n    return parsed as Record<string, string | null>;\n  } catch {\n    throw new Error('Linear cursor is invalid.');\n  }\n}\n\nfunction encodeCursor(state: Record<string, string | null>, sourceIds: string[]): string {\n  if (sourceIds.length === 1) return state[sourceIds[0]!]!;\n  return JSON.stringify(state);\n}\n\nfunction requireLinearConnection(connection: IntegrationConnection): void {\n  if (connection.type !== 'oauth') {\n    throw new Error('Linear capabilities require an OAuth connection.');\n  }\n}\n\nfunction isNotFound(error: unknown): boolean {\n  return error instanceof PlatformApiError && error.status === 404;\n}\n","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/integrations/platform/linear/integration.ts#L653-L689","documentation":"For multi-project Linear sync the cursor is a JSON object mapping each source id to its last-seen cursor value. `decodeCursor` only parses JSON when there is more than one sourceId; if the stored cursor is not parseable JSON, is not a plain object (array, null, scalar), it throws 'Linear cursor is invalid.' This prevents restoring a resume position that would corrupt per-source bookmark tracking.","triggerScenarios":"Calling pull/sync with multiple sourceIds and a cursor string that is not a JSON object — e.g. a single-source raw cursor string, 'null', '\"text\"', '[1,2]', or truncated JSON from storage.","commonSituations":"A sync that started with one project (cursor saved as a bare string) later expanded to multiple projects; cursor field shrunk/truncated by a DB migration; operator pasting a single-source cursor into a multi-project job.","solutions":["Start the multi-source sync with no cursor (undefined/null) so it builds a fresh object-shaped cursor.","Convert legacy single cursors: wrap them as `{ [sourceId]: legacyCursor }` before passing.","Ensure whatever persists the cursor does not truncate the JSON string.","Catch this error and fall back to a full re-sync from a null cursor."],"exampleFix":"// before\nawait integration.pull({ sourceIds: [idA, idB], cursor: savedSingleCursor });\n// after\nconst parsed = savedSingleCursor?.startsWith('{') ? savedSingleCursor : JSON.stringify({ [idA]: savedSingleCursor });\nawait integration.pull({ sourceIds: [idA, idB], cursor: parsed });","handlingStrategy":"fallback","validationCode":"function safeDecodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null> {\n  if (!cursor || sourceIds.length === 1) return {};\n  try {\n    const p: unknown = JSON.parse(cursor);\n    if (p && typeof p === 'object' && !Array.isArray(p)) return p as Record<string, string | null>;\n  } catch { /* fall through */ }\n  return {}; // invalid cursor => fresh sync\n}","typeGuard":"function isCursorMap(v: unknown): v is Record<string, string | null> {\n  return v !== null && typeof v === 'object' && !Array.isArray(v) &&\n    Object.values(v).every(x => typeof x === 'string' || x === null);\n}","tryCatchPattern":"try {\n  await sync({ cursor: savedCursor });\n} catch (err) {\n  if (err instanceof Error && err.message === 'Linear cursor is invalid.') {\n    await sync({ cursor: undefined }); // full re-sync\n  } else throw err;\n}","preventionTips":["Migrate single-source cursors to `{ [sourceId]: cursor }` when adding more projects.","Wrap the stored cursor in a versioned envelope so shape changes are detectable.","On decode failure, prefer a null-cursor full re-sync over retrying the bad value."],"tags":["linear","cursor","parsing","json"],"backgroundTag":"invalid-cursor-format","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}