Skip to content

Conversation

@chadrwalters
Copy link

Summary

Add linearis embeds upload <file> command that uploads local files to Linear's cloud storage and returns the asset URL for use in markdown.

This mirrors the existing embeds download command to provide complete upload/download capability.

Changes

  • Add uploadFile() method to FileService using GraphQL fileUpload mutation
  • Add upload subcommand to embeds command group
  • Add MIME type detection for common file types (images, documents, text, archives, video/audio)
  • Add 20MB file size limit validation
  • Add comprehensive unit tests (12 tests)

How it works

  1. Validates file exists and is under 20MB
  2. Calls Linear's fileUpload GraphQL mutation to get pre-signed URL
  3. PUTs file content to the pre-signed URL with appropriate headers
  4. Returns asset URL for embedding in comments/descriptions

Example usage

# Upload a file
linearis embeds upload ./screenshot.png
# Returns: { "success": true, "assetUrl": "https://uploads.linear.app/...", "filename": "screenshot.png" }

# Use with comments
URL=$(linearis embeds upload ./bug.png | jq -r .assetUrl)
linearis comments create ABC-123 --body "See attached: ![$URL]($URL)"

Testing

  • 12 new unit tests covering:
    • Successful uploads
    • MIME type detection
    • File not found errors
    • File size validation
    • GraphQL error handling
    • PUT request failures

All tests pass locally.

Context

This feature was requested to support file attachments in Linear ticket comments from CLI tools like Huginn. Currently Huginn has to bypass Linearis and call Linear's GraphQL API directly for file uploads - this PR allows it to use Linearis consistently for all Linear operations.

Add `linearis embeds upload <file>` command that uploads local files to
Linear's cloud storage and returns the asset URL for use in markdown.

Changes:
- Add uploadFile() method to FileService using GraphQL fileUpload mutation
- Add upload subcommand to embeds command group
- Add MIME type detection for common file types
- Add 20MB file size limit validation
- Add comprehensive unit tests (12 tests)

The upload process:
1. Validates file exists and is under 20MB
2. Calls Linear's fileUpload GraphQL mutation to get pre-signed URL
3. PUTs file content to the pre-signed URL
4. Returns asset URL for embedding in comments/descriptions

Example usage:
  linearis embeds upload ./screenshot.png
  # Returns: { "success": true, "assetUrl": "https://uploads.linear.app/..." }

Closes #ENG-2183
Copy link
Owner

@czottmann czottmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @chadrwalters! Good PR. I'd like to request a few changes, though.

🚨 Bug: Authentication header format

In file-service.ts:298:

Authorization: this.apiToken,

Linear's API requires Authorization: Bearer <ACCESS_TOKEN> format. The existing download code correctly uses this at line 113:

headers.Authorization = `Bearer ${this.apiToken}`;

But the upload code omits the Bearer prefix. This will cause 401 authentication failures.

Required fix:

Authorization: `Bearer ${this.apiToken}`,

This bug will cause production failures and must be fixed before merge, please.

⚠️ Minor issue

Unused parameter in embeds.ts:99:

async (filePath: string, options: any, command: Command) => {

The options parameter is declared but never used. Should be _options to indicate intentional non-use.

💭 Questions / observations

Direct GraphQL vs service reuse - What's the reason for not using the existing GraphQLService for the fileUpload mutation? The current approach duplicates error handling patterns. (This might be intentional to keep FileService self-contained - but I'm curious about the reasoning.)

20MB limit - The limit matches Linear's constraints, but a comment noting the source would help future maintainers know whether it's a Linear-imposed limit or a design choice.

- Add Bearer prefix to Authorization header for GraphQL API calls
- Rename unused `options` parameter to `_options` in upload action
- Add source comment for 20MB file size limit (Linear API constraint)
- Update test to expect Bearer prefix in Authorization header

Fixes auth header inconsistency between upload and download code paths.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@chadrwalters
Copy link
Author

Thanks for the thorough review, @czottmann!

🚨 Bug: Authentication header format

Fixed. Good catch - I added the Bearer prefix to match the download code pattern at line 182. Updated the test as well.

⚠️ Unused parameter

Fixed. Renamed options_options to indicate intentional non-use. Commander.js passes it, but the upload command doesn't need options currently.

💭 Direct GraphQL vs service reuse

Intentionally kept FileService self-contained. The upload flow is a two-step process (GraphQL mutation → PUT to pre-signed URL) with specific headers from the mutation response. Using GraphQLService would have required either:

  1. Exposing internal rawRequest() method
  2. Making GraphQLService aware of file upload headers

Keeping it in FileService felt cleaner for this specialized flow. Happy to discuss if you'd prefer a different approach.

💭 20MB limit

Fixed. Added a doc comment citing Linear's fileUpload API as the source:

/**
 * Maximum file size for uploads (20MB)
 * This limit is imposed by Linear's fileUpload API.
 * See: https://linear.app/developers/graphql/fileupload
 */

All fixes are in commit 0a19ae3.

@czottmann czottmann merged commit ecd957d into czottmann:main Dec 11, 2025
2 checks passed
@czottmann
Copy link
Owner

Thank you, @chadrwalters!

czottmann added a commit that referenced this pull request Dec 11, 2025
Document new features from recent PRs:
- embeds upload command (PR #23)
- documents commands (PR #21)

Also add Ralf Schimmel to contributors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants