mastra-ai/mastra · error

Linear project source id is invalid.

Error message

Linear project source id is invalid.

What it means

Cursor/source IDs for Linear projects are encoded as `linear-project:` followed by a base64url JSON payload of `{workspaceId, projectId}`. `decodeSourceId` throws this error when the incoming string lacks the `linear-project:` prefix, or when the decoded payload fails validation (malformed JSON, missing/empty workspaceId or projectId, bad base64url). It guards against persisting or resolving state against a corrupted or foreign source id.

Source

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

function parseIssueDetail(issue: LinearIssue, comments: LinearComment[]): IntakeIssueDetail {
  return {
    ...parseIssue(issue),
    commentCount: comments.length,
    description: issue.description?.trim() ? issue.description : null,
    comments: comments.map(comment => ({
      author: comment.user?.displayName ?? comment.user?.name ?? null,
      body: comment.body,
      createdAt: comment.createdAt,
    })),
  };
}

function encodeSourceId(workspaceId: string, projectId: string): string {
  return `linear-project:${Buffer.from(JSON.stringify({ workspaceId, projectId })).toString('base64url')}`;
}

function decodeSourceId(sourceId: string): { workspaceId: string; projectId: string } {
  if (!sourceId.startsWith('linear-project:')) throw new Error('Linear project source id is invalid.');
  try {
    const parsed = JSON.parse(Buffer.from(sourceId.slice('linear-project:'.length), 'base64url').toString('utf8')) as {
      workspaceId?: unknown;
      projectId?: unknown;
    };
    if (typeof parsed.workspaceId !== 'string' || !parsed.workspaceId) throw new Error();
    if (typeof parsed.projectId !== 'string' || !parsed.projectId) throw new Error();
    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> {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Only pass source ids that were produced by `encodeSourceId` (i.e. returned by the integration itself).
  2. If the id came from persisted storage written by an older version, re-derive it by re-fetching the Linear project and re-encoding `{workspaceId, projectId}`.
  3. Check the value for the 'linear-project:' prefix before use to identify cross-platform contamination.
  4. Wrap decode-bearing calls in try/catch and re-sync cursors from scratch when decoding fails.

Example fix

// before
await integration.pull({ sourceId: stored.id }); // stored.id = 'github-project:abc'
// after
if (!stored.id.startsWith('linear-project:')) {
  const fresh = await encodeSourceId(workspaceId, projectId);
}
await integration.pull({ sourceId: stored.id.startsWith('linear-project:') ? stored.id : fresh });
Defensive patterns

Strategy: type-guard

Validate before calling

function isLinearProjectSourceId(id: unknown): id is string {
  if (typeof id !== 'string' || !id.startsWith('linear-project:')) return false;
  try {
    const p = JSON.parse(Buffer.from(id.slice('linear-project:'.length), 'base64url').toString('utf8'));
    return typeof p?.workspaceId === 'string' && p.workspaceId.length > 0 &&
           typeof p?.projectId === 'string' && p.projectId.length > 0;
  } catch { return false; }
}

Type guard

function isLinearProjectSourceId(id: unknown): id is string {
  return typeof id === 'string' && id.startsWith('linear-project:') &&
    (() => { try {
      const p = JSON.parse(Buffer.from(id.slice(15), 'base64url').toString('utf8'));
      return typeof p.workspaceId === 'string' && !!p.workspaceId && typeof p.projectId === 'string' && !!p.projectId;
    } catch { return false; } })();
}

Try / catch

try {
  await integration.pull({ sourceId });
} catch (err) {
  if (err instanceof Error && err.message === 'Linear project source id is invalid.') {
    // re-encode or re-sync from scratch
  } else throw err;
}

Prevention

When it happens

Trigger: Calling any integration API that decodes a source id with a value that (a) does not start with 'linear-project:', (b) has a suffix that is not valid base64url, (c) decodes to JSON without string workspaceId/projectId. Commonly from a cursor or sourceId loaded from stale storage or hand-crafted.

Common situations: Restoring a saved cursor/source id from an older library version with a different encoding; manually constructing source ids instead of using the value returned by the integration; database rows written by another platform's integration (e.g. a github-project id passed to the Linear decoder).

Related errors


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