linear-fuse

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT

README

LinearFS

Mount your Linear workspace as a FUSE filesystem. Browse and edit issues as markdown files.

Project status

LinearFS is a v0 tool — capable and daily-usable, but with no stability guarantees. Breaking changes are acceptable and expected. It is solo-maintained with heavy AI assistance, and contributions are welcome — see CONTRIBUTING.md.

  • For: CLI-first people who'd rather ls/cat/grep/vim their issues than click a UI, and anyone scripting against Linear through ordinary file operations.
  • Not for: real-time collaboration (sync is eventually consistent) or production tooling that needs a stable API — use Linear's official API for that.
  • Handling secrets? Read SECURITY.md before mounting, especially on a shared machine.
  • Known limitations are tracked in KNOWN_ISSUES.md.

Features

  • Browse teams, issues, projects, initiatives, and labels as directories/files
  • Issues rendered as markdown with YAML frontmatter
  • Edit frontmatter to update issue status, assignee, priority, labels
  • Full CRUD for comments, documents, and labels
  • Create/archive issues and projects with standard filesystem operations
  • Multiple views: by team, by user, personal (assigned/created/active)
  • Initiatives with linked projects

Installation

See INSTALL.md for detailed platform-specific installation instructions.

Quick Start
# Build from source
make build

# Install to ~/bin
make install
Requirements
  • Go 1.25+
  • FUSE filesystem:
    • macOS: macFUSE (brew install --cask macfuse)
      • ⚠️ Apple Silicon requires enabling kernel extensions in Recovery Mode
    • Linux: FUSE3 (sudo pacman -S fuse3 on Arch, sudo apt install fuse3 on Ubuntu/Debian)

Usage

# Set your Linear API key
export LINEAR_API_KEY="lin_api_xxxxx"

# Mount the filesystem
linearfs mount ~/linear

# Browse your issues (replace TEAM with your team key)
ls ~/linear/teams/
ls ~/linear/teams/TEAM/issues/
cat ~/linear/teams/TEAM/issues/TEAM-123/issue.md

# View comments on an issue
ls ~/linear/teams/TEAM/issues/TEAM-123/comments/
cat ~/linear/teams/TEAM/issues/TEAM-123/comments/001-2025-01-10T14-30.md

# Add a comment
echo "My comment" > ~/linear/teams/TEAM/issues/TEAM-123/comments/_create

# View your assigned issues
ls ~/linear/my/assigned/

# Unmount
# macOS
umount ~/linear

# Linux
fusermount3 -u ~/linear

# or Ctrl+C if running in foreground

File Permissions

Use ls -l to see what operations are allowed on each file:

Permission Meaning Example
-r--r--r-- Read-only team.md, states.md, initiative.md
-rw-r--r-- Editable issue.md, project.md, existing docs/comments
--w------- Write-only trigger _create (creates new items)
lrwxrwxrwx Symlink Issues in cycles/projects/filtered views

Important: Existing documents and comments are editable. Edit them directly—don't write to _create to update existing content.

File Timestamps

Files and directories have meaningful timestamps from Linear, enabling time-based sorting:

# Sort by modification time (most recently updated first)
ls -lt ~/linear/my/active/
ls -lt ~/linear/teams/TEAM/by/status/Todo/

# Sort by creation time (oldest first)
ls -ltr ~/linear/teams/TEAM/issues/

Note: If using eza (aliased as ls), use ls --sort=modified instead of ls -lt.

Timestamp Source Example
mtime (modified) Issue's updatedAt Last edit to issue
ctime (changed) Issue's createdAt When issue was created
atime (accessed) Same as mtime Not separately tracked

Timestamps are preserved across all views:

  • Issue directories and symlinks in /my/, /users/, /by/, /cycles/, /projects/
  • Project and initiative directories
  • Cycle directories (use cycle start/end dates)

Note: All files are owned by the user who mounted the filesystem, not root.

Directory Structure

~/linear/
├── README.md                    # In-filesystem documentation
├── teams/
│   └── <TEAM>/                  # Your team key (e.g., ENG, PROD)
│       ├── team.md              # Team metadata (read-only)
│       ├── states.md            # Workflow states (read-only)
│       ├── labels.md            # Labels reference (read-only)
│       ├── by/                  # Filter issues by attribute
│       │   ├── status/<name>/   # Issues filtered by status (symlinks)
│       │   ├── label/<name>/    # Issues filtered by label (symlinks)
│       │   └── assignee/<name>/ # Issues by assignee (includes "unassigned")
│       ├── issues/
│       │   └── <TEAM-nnn>/       # Issue identifier (e.g., TEAM-123)
│       │       ├── issue.md     # Issue content (read/write)
│       │       ├── comments/
│       │       │   ├── 001-*.md # Comments (read/write/delete)
│       │       │   └── _create   # Write here to create comment
│       │       ├── docs/
│       │       │   ├── *.md     # Issue documents (read/write/rename/delete)
│       │       │   └── _create   # Write here to create document
│       │       ├── children/    # Sub-issues (symlinks to sibling issues)
│       │       └── .error       # Last validation error (read-only)
│       ├── labels/              # Label management
│       │   ├── *.md             # Labels (read/write/rename/delete)
│       │   └── _create           # Write here to create label
│       ├── docs/                # Team documents
│       │   ├── *.md             # Documents (read/write/rename/delete)
│       │   └── _create           # Write here to create document
│       ├── cycles/              # Sprint cycles
│       │   ├── current          # Symlink to active cycle (if any)
│       │   └── <cycle-name>/    # Cycle directories with issue symlinks
│       └── projects/
│           └── <project-slug>/
│               ├── project.md   # Project metadata (read/write)
│               ├── docs/        # Project documents
│               ├── updates/     # Status updates (write to _create)
│               └── TEAM-*       # Symlinks to issue directories
├── initiatives/
│   └── <initiative-slug>/
│       ├── initiative.md        # Initiative metadata (read-only)
│       ├── projects/            # Symlinks to team projects
│       └── updates/             # Status updates (write to _create)
├── users/
│   └── <username>/
│       ├── user.md              # User metadata (read-only)
│       └── TEAM-*               # Symlinks to issue directories
└── my/
    ├── assigned/                # Issues assigned to you
    ├── created/                 # Issues you created
    └── active/                  # Non-completed assigned issues

Issue File Format

---
id: "abc123-def456"
identifier: TEAM-123
url: "https://linear.app/myworkspace/issue/TEAM-123"
created: 2025-01-10T10:30:00Z
updated: 2025-01-11T14:22:00Z
title: "Fix authentication bug"
status: "In Progress"
assignee: "alice@example.com"
priority: high
labels:
  - bug
  - backend
parent: TEAM-100
---

The login flow fails when users attempt to authenticate with SSO.
Editable Fields
  • title - Issue title
  • status - Workflow state name (check states.md for valid values)
  • assignee - User email or name
  • priority - none/low/medium/high/urgent
  • labels - List of label names (check labels.md for valid values)
  • due - Due date (YYYY-MM-DD format)
  • estimate - Point estimate
  • parent - Parent issue identifier (e.g., TEAM-100)
  • Description (content after frontmatter)
Validation Errors

Writes fail with EINVAL (Invalid argument) for invalid frontmatter values. After a failed write, check the .error file to see what went wrong:

# Example: invalid priority value
$ echo "priority: critical" >> ~/linear/teams/TEAM/issues/TEAM-123/issue.md
# Write fails with EINVAL

$ cat ~/linear/teams/TEAM/issues/TEAM-123/.error
Field: priority
Value: "critical"
Error: invalid priority "critical": must be none, low, medium, high, or urgent

Validated fields: status, assignee, labels, priority, project, milestone, cycle, parent

Reference files: Check states.md for valid workflow states, labels.md for valid labels.

The .error file is cleared on successful writes.

File Operations

LinearFS maps standard filesystem operations to Linear API actions:

Issues
Operation Command Effect
Create issue mkdir issues/"Issue title" Creates new issue with title
Archive issue rmdir issues/TEAM-123 Archives issue (soft delete)
Edit issue Edit issue.md and save Updates issue fields
# Create a new issue
mkdir ~/linear/teams/TEAM/issues/"Fix login bug"

# Archive an issue
rmdir ~/linear/teams/TEAM/issues/TEAM-123
Sub-Issues
Operation Command Effect
View sub-issues ls issues/TEAM-123/children/ Lists child issues as symlinks
Set parent Edit parent: in issue.md Sets parent issue
Remove parent Remove parent: line Clears parent relationship
# View sub-issues of TEAM-123
ls ~/linear/teams/TEAM/issues/TEAM-123/children/

# Set parent by editing frontmatter (editors work here, unlike _create)
# Add: parent: TEAM-100
vim ~/linear/teams/TEAM/issues/TEAM-456/issue.md
Comments
Operation Command Effect
Read comments cat comments/001-*.md View comment content
Create comment echo "text" > comments/_create Posts new comment
Edit comment Edit comment file and save Updates comment
Delete comment rm comments/001-*.md Deletes comment

Note: _create is a write-only trigger file. It's always empty (0 bytes) and cannot be read. Write content to it using echo or cat with redirect. Editors that read before writing won't work.

# Add a comment
echo "This needs review" > ~/linear/teams/TEAM/issues/TEAM-123/comments/_create

# Delete a comment
rm ~/linear/teams/TEAM/issues/TEAM-123/comments/001-2025-01-10T14-30.md
Documents
Operation Command Effect
Create document echo "..." > docs/_create Creates document with title from frontmatter
Edit document Edit doc file and save Updates title/content
Rename document mv docs/old.md docs/_create Renames document title
Delete document rm docs/spec.md Deletes document

Note: _create is a write-only trigger file (see Comments section above).

# Create a document (with YAML frontmatter for title)
cat > ~/linear/teams/TEAM/issues/TEAM-123/docs/_create << 'EOF'
---
title: "Technical Spec"
---
Document content here...
EOF

# Rename a document
mv docs/old-name.md docs/new-name.md
Labels
Operation Command Effect
Create label echo "..." > labels/_create Creates label with name/color
Edit label Edit label file and save Updates name/color/description
Rename label mv labels/Bug.md labels/Defect.md Renames label
Delete label rm labels/OldLabel.md Deletes label

Note: _create is a write-only trigger file (see Comments section above).

# Create a new label
cat > ~/linear/teams/TEAM/labels/_create << 'EOF'
---
name: "Critical"
color: "#FF0000"
description: "Critical priority items"
---
EOF

# Rename a label
mv ~/linear/teams/TEAM/labels/Bug.md ~/linear/teams/TEAM/labels/Defect.md

# Delete a label
rm ~/linear/teams/TEAM/labels/OldLabel.md
Projects
Operation Command Effect
Create project mkdir projects/"Project Name" Creates new project
Archive project rmdir projects/project-slug Archives project (soft delete)
# Create a new project
mkdir ~/linear/teams/TEAM/projects/"Q1 Launch"

# Archive a project
rmdir ~/linear/teams/TEAM/projects/q1-launch
Team Documents

Teams can have their own documents separate from issues:

Operation Command Effect
List documents ls teams/TEAM/docs/ Shows team documents
Create document echo "..." > teams/TEAM/docs/"Title.md" Creates document with title
Edit document Edit doc file and save Updates title/content
Delete document rm teams/TEAM/docs/spec.md Deletes document
# Create a team document
cat > /mnt/linear/teams/TEAM/docs/"Engineering Standards.md" << 'EOF'
---
title: "Engineering Standards"
---
Our coding standards and best practices...
EOF
Project Updates

Post status updates to projects with health indicators:

Operation Command Effect
Create update echo "..." > projects/slug/updates/_create Posts status update
View updates ls projects/slug/updates/ Lists all updates
# Post a project status update
cat > /mnt/linear/teams/TEAM/projects/q1-launch/updates/_create << 'EOF'
---
health: atRisk
---
We're blocked on external API dependencies. ETA for resolution is next week.
EOF

Health values: onTrack, atRisk, offTrack

Editing Labels on Issues

Edit the labels array in an issue's frontmatter:

---
title: "Fix bug"
status: "In Progress"
labels:
  - Bug
  - Backend
  - Critical
---

Save the file to update the issue's labels in Linear.

Caching Strategy

LinearFS caches data locally to minimize API calls and provide responsive filesystem operations. Since Linear's real-time sync engine is not exposed in their public API, LinearFS uses a TTL-based polling strategy with immediate invalidation on writes.

How It Works
Read:   Filesystem → Cache hit? → Return cached data
                   → Cache miss? → Fetch from Linear API → Cache → Return

Write:  Filesystem → Update via Linear API → Invalidate relevant caches
TTL Values
Data Type Default TTL Rationale
Issues 60s Change frequently
Comments 60s Change frequently
Documents 60s Change frequently
Projects 60s Moderate change rate
Cycles 60s Change with issues
States 10 minutes Workflow states rarely change
Labels 10 minutes Team labels rarely change
Users 10 minutes Team membership rarely changes
Write-Through Invalidation

When you modify data through LinearFS, caches are immediately invalidated:

  • Edit issue → Invalidates team issues, my issues, user issues caches
  • Add comment → Invalidates comment cache for that issue
  • Archive issue → Invalidates team, my, and assignee issue caches
  • Create/delete label → Invalidates team labels cache

This means your own changes appear immediately, but changes made by others (in the Linear app or API) appear after TTL expiry.

FUSE Kernel Caching

In addition to the application-level cache, the Linux kernel caches filesystem attributes:

  • Entry timeout: 30 seconds (directory listings)
  • Attr timeout: 30 seconds (file metadata)

This reduces kernel-to-userspace calls but means ls output may lag slightly behind cache invalidations.

Configuring TTL

Adjust the base TTL in your config file:

cache:
  ttl: 60s    # Base TTL (states/labels/users get 10x this value)

Lower values = fresher data but more API calls. Higher values = better performance but staler data.

Limitations
  • No real-time sync: Linear's WebSocket-based sync engine is internal only; the public API offers webhooks (requires HTTP server) but not subscriptions
  • Eventual consistency: Changes by teammates appear after TTL expiry
  • Rate limits: Linear meters API keys on two axes — request count and query complexity — and reports both on every response. LinearFS governs itself against the live limits from those headers (a priority ladder sheds background detail fetches first). Bulk reads over a large workspace can still exhaust the hourly budget; reads then fall back to the local cache.

Configuration

Create ~/.config/linearfs/config.yaml:

api_key: "lin_api_xxxxx"  # or use LINEAR_API_KEY env var

cache:
  ttl: 60s

mount:
  default_path: ~/linear

log:
  level: info

Running as a Service

macOS (launchd)

To start LinearFS automatically on login:

# Install the service
cp contrib/launchd/com.linearfs.mount.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.linearfs.mount.plist
launchctl start com.linearfs.mount

# Your Linear workspace will now be mounted at ~~/linear on every login

See INSTALL.md for details.

Linux (systemd)
# Install the service
mkdir -p ~/.config/systemd/user
cp contrib/systemd/linearfs.service ~/.config/systemd/user/
systemctl --user enable linearfs.service
systemctl --user start linearfs.service

See INSTALL.md for details.

Contributing

Contributions are welcome. Start with CONTRIBUTING.md — it explains the development workflow, the testing philosophy, and the two living design docs (CONTEXT.md and docs/ARCHITECTURE.md) that keep the codebase navigable. Bugs and feature ideas go through the issue templates; security reports go through SECURITY.md.

License

MIT © John Allen

Directories

Path Synopsis
cmd
linearfs command
internal
api
cmd
db
fs
reconcile
Package reconcile owns the reconcile-into-SQLite tails shared by the sync worker and (in a later step) the repo's SWR refresh path: the upsert-all/prune-if-clean collection tail (Collection), the per-issue detail persist composed from it (PersistIssueDetails), and the embedded-file extraction module (Extractor).
Package reconcile owns the reconcile-into-SQLite tails shared by the sync worker and (in a later step) the repo's SWR refresh path: the upsert-all/prune-if-clean collection tail (Collection), the per-issue detail persist composed from it (PersistIssueDetails), and the embedded-file extraction module (Extractor).
sync
Package sync implements background synchronization of Linear issues to SQLite.
Package sync implements background synchronization of Linear issues to SQLite.
telemetry
Package telemetry owns the OTEL metrics pipeline for linearfs.
Package telemetry owns the OTEL metrics pipeline for linearfs.
testutil
Package testutil provides test utilities including a mock Linear API server.
Package testutil provides test utilities including a mock Linear API server.
testutil/mockmutation
Package mockmutation provides an in-memory fake of the Linear mutation surface (the fs.MutationClient interface) for offline tests.
Package mockmutation provides an in-memory fake of the Linear mutation surface (the fs.MutationClient interface) for offline tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL