mastra-ai/mastra · error

Linear cursor is invalid.

Error message

Linear cursor is invalid.

What it means

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.

Source

Thrown at mastracode/factory/src/integrations/platform/linear/integration.ts:671

    return { workspaceId: parsed.workspaceId, projectId: parsed.projectId };
  } catch {
    throw new Error('Linear project source id is invalid.');
  }
}

function normalizeLabels(labels: string[] | undefined): string[] {
  return [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];
}

function decodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null | undefined> {
  if (!cursor) return {};
  if (sourceIds.length === 1) return { [sourceIds[0]!]: cursor };
  try {
    const parsed = JSON.parse(cursor) as unknown;
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error();
    return parsed as Record<string, string | null>;
  } catch {
    throw new Error('Linear cursor is invalid.');
  }
}

function encodeCursor(state: Record<string, string | null>, sourceIds: string[]): string {
  if (sourceIds.length === 1) return state[sourceIds[0]!]!;
  return JSON.stringify(state);
}

function requireLinearConnection(connection: IntegrationConnection): void {
  if (connection.type !== 'oauth') {
    throw new Error('Linear capabilities require an OAuth connection.');
  }
}

function isNotFound(error: unknown): boolean {
  return error instanceof PlatformApiError && error.status === 404;
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Start the multi-source sync with no cursor (undefined/null) so it builds a fresh object-shaped cursor.
  2. Convert legacy single cursors: wrap them as `{ [sourceId]: legacyCursor }` before passing.
  3. Ensure whatever persists the cursor does not truncate the JSON string.
  4. Catch this error and fall back to a full re-sync from a null cursor.

Example fix

// before
await integration.pull({ sourceIds: [idA, idB], cursor: savedSingleCursor });
// after
const parsed = savedSingleCursor?.startsWith('{') ? savedSingleCursor : JSON.stringify({ [idA]: savedSingleCursor });
await integration.pull({ sourceIds: [idA, idB], cursor: parsed });
Defensive patterns

Strategy: fallback

Validate before calling

function safeDecodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null> {
  if (!cursor || sourceIds.length === 1) return {};
  try {
    const p: unknown = JSON.parse(cursor);
    if (p && typeof p === 'object' && !Array.isArray(p)) return p as Record<string, string | null>;
  } catch { /* fall through */ }
  return {}; // invalid cursor => fresh sync
}

Type guard

function isCursorMap(v: unknown): v is Record<string, string | null> {
  return v !== null && typeof v === 'object' && !Array.isArray(v) &&
    Object.values(v).every(x => typeof x === 'string' || x === null);
}

Try / catch

try {
  await sync({ cursor: savedCursor });
} catch (err) {
  if (err instanceof Error && err.message === 'Linear cursor is invalid.') {
    await sync({ cursor: undefined }); // full re-sync
  } else throw err;
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/2cfdf44ce4821c8b. Report an issue: GitHub.