Skip to main content
Generalmarkus41

Linear Attachments

This skill should be used when uploading files to Linear, linking external URLs as attachments, or downloading existing attachments with auth. Activates on "linear attachment", "linear file upload", "fileUpload mutation", "attachmentLinkCreate".

Stars
12
Source
markus41/claude
Updated
2026-05-11
Slug
markus41--claude--linear-attachments
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/markus41/claude/HEAD/plugins/linear-orchestrator/skills/linear-attachments/SKILL.md -o .claude/skills/linear-attachments.md

Drops the SKILL.md into .claude/skills/linear-attachments.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

Linear Attachments

References:

Two flavours

Type Storage Use case
File attachment Linear S3 (pre-signed) screenshots, logs, design files
Link attachment External URL only GitHub PR, Figma, Notion, Loom

File upload (3-step)

// 1. Reserve upload URL
const reservation = await client.fileUpload({
  contentType: "image/png",
  filename: "screenshot.png",
  size: file.length
});
// { uploadFile: { uploadUrl, headers: [{key,value}], assetUrl } }

// 2. PUT bytes
await fetch(reservation.uploadUrl, {
  method: "PUT",
  body: file,
  headers: Object.fromEntries(reservation.headers.map(h => [h.key, h.value]))
});

// 3. Attach to issue
await client.attachmentCreate({
  issueId,
  url: reservation.assetUrl,
  title: "Screenshot",
  contentType: "image/png"
});

Helper in lib/attachment-upload.ts:

export async function uploadFileToLinear(
  client: LinearClient,
  issueId: string,
  filePath: string,
  title?: string
): Promise<string>;  // returns attachment ID

Multipart for large files

For files >50MB:

  • Linear's pre-signed URL supports multipart S3 uploads
  • Split into 5MB parts, PUT each with partNumber query param
  • Complete with POST ?uploads to finalise

Most use cases stay under 50MB; only logs and video do this.

Link attachments

await client.attachmentLinkCreate(issueId, "https://github.com/org/repo/pull/42", "PR #42");

Linear infers preview from URL pattern. Supported: GitHub PR / issue / commit, Figma, Notion, Loom, Vimeo, Slack thread, generic OG-rich pages.

Download with auth

Asset URLs require the same Linear token in Authorization: Bearer <token>. Don't share these URLs publicly — they expire after 7 days but exposing during that window is a leak.

const res = await fetch(assetUrl, {
  headers: { Authorization: `Bearer ${process.env.LINEAR_API_KEY}` }
});
const buffer = Buffer.from(await res.arrayBuffer());

Or, mint a short-lived signed URL through your own service if you need to share with end users.

Bridge fan-out

  • Harness Code: file attachments are linked (not copied) to the linked PR via a comment with the asset URL — the URL is auth-required, so reviewers must be Linear members.
  • MS Planner: attachments uploaded to Linear are mirrored to OneDrive/SharePoint via Microsoft Graph driveItem upload. The Planner task references field gets the OneDrive URL.

Security notes

  • Validate content-type before declaring it (don't trust user input)
  • Cap upload size at the layer above (usually 100MB)
  • Strip EXIF from images if client-side is feasible
  • Don't pass user file bytes through your backend; redirect them to Linear's pre-signed URL directly