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
- Only pass source ids that were produced by `encodeSourceId` (i.e. returned by the integration itself).
- 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}`.
- Check the value for the 'linear-project:' prefix before use to identify cross-platform contamination.
- 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
- Always use source ids returned by the integration, never hand-built strings.
- Tag persisted source ids with the integration kind so cross-platform ids cannot be mixed.
- Validate stored ids on load with the prefix check before use.
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
- Linear cursor is invalid.
- CursorSDKAgent resumeData must include a message.
- CursorSDKAgent resumeData.agentId must be a string when prov
- CursorSDKAgent does not support structuredOutput because the
- GitHub cursor must be a positive page number.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/6df997fa89b63a4f.
Report an issue: GitHub.