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
---
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
| 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