-
Notifications
You must be signed in to change notification settings - Fork 9
feat(plugins): add linear-triage plugin #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
shannonwho
wants to merge
2
commits into
main
Choose a base branch
from
feature/linear-triage-plugin
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "name": "linear-triage", | ||
| "version": "0.1.0", | ||
| "description": "Claude Code plugin that polls a Linear team's triage queue for issues labeled 'Claude Code', then implements each one end-to-end (branch, PR, tests, status updates).", | ||
| "author": { | ||
| "name": "Shannon Hu", | ||
| "email": "shannon.hu@linear.app" | ||
| }, | ||
| "homepage": "https://github.com/linear/linear-solutions/tree/main/plugins/linear-triage", | ||
| "license": "MIT", | ||
| "keywords": ["linear", "triage", "automation", "agent"], | ||
| "skills": [ | ||
| "skills/linear-triage-setup", | ||
| "skills/linear-triage-poller", | ||
| "skills/linear-issue-worker" | ||
| ], | ||
| "requirements": { | ||
| "mcp": ["claude_ai_Linear"], | ||
| "cli": ["gh", "git"] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .DS_Store | ||
| node_modules/ | ||
| *.log | ||
| config.json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # Linear Triage Plugin for Claude Code | ||
|
|
||
| Polls a Linear team's triage queue for issues labeled **"Claude Code"** and implements each one end-to-end: reads the issue, plans, creates a worktree on Linear's `gitBranchName`, implements, opens a draft PR, runs tests, and marks the PR ready for review. Every stage transition is posted as a comment on the Linear issue, so the issue stays the single source of truth. | ||
|
|
||
| Built on the [Symphony](https://github.com/openai/symphony) pattern: tracker-as-queue + isolated worktree per issue + agent-per-issue. | ||
|
|
||
| See [`docs/architecture.md`](docs/architecture.md) for a diagram of how the three skills coordinate with the Linear MCP server. | ||
|
|
||
| ## Install | ||
|
|
||
| ```bash | ||
| # 1. Clone the linear-solutions monorepo | ||
| git clone https://github.com/linear/linear-solutions ~/Claude/linear-solutions | ||
|
|
||
| # 2. Copy the example config | ||
| cp ~/Claude/linear-solutions/plugins/linear-triage/config.example.json ~/.claude/linear-triage-config.json | ||
| $EDITOR ~/.claude/linear-triage-config.json # set repo, repoUrl, team | ||
|
|
||
| # 3. Launch Claude Code with the plugin loaded | ||
| claude --plugin-dir ~/Claude/linear-solutions/plugins/linear-triage | ||
| ``` | ||
|
|
||
| > The `--plugin-dir` flag loads a local plugin for the session. To load it every time, add an alias (e.g. `alias claude-triage='claude --plugin-dir ~/Claude/linear-solutions/plugins/linear-triage'`) or pass multiple `--plugin-dir` flags to combine plugins. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - `gh` CLI authenticated (`gh auth status`) | ||
| - `git` available on PATH | ||
| - Claude Linear MCP connected (the `mcp__claude_ai_Linear__*` tools must be available) | ||
| - A target git repo cloned locally (the plugin will clone for you if missing) | ||
|
|
||
| ## Setup | ||
|
|
||
| In Claude Code, run: | ||
|
|
||
| ``` | ||
| /linear-triage-setup | ||
| ``` | ||
|
|
||
| This: | ||
|
|
||
| 1. Verifies prerequisites | ||
| 2. Creates the **"Claude Code"** label in Linear (if missing) | ||
| 3. Registers a `RemoteTrigger` cron `*/15 * * * *` that runs `/linear-triage-poller` | ||
|
|
||
| ## Daily use | ||
|
|
||
| Tag any Linear triage issue with the **"Claude Code"** label. Within 15 minutes the poller picks it up, claims it (moves to "In Progress"), and spawns a worker that delivers a draft PR. | ||
|
|
||
| To trigger immediately: `/linear-triage-poller` | ||
|
|
||
| ## Configuration | ||
|
|
||
| `~/.claude/linear-triage-config.json`: | ||
|
|
||
| | Key | Purpose | | ||
| |---|---| | ||
| | `label` | Linear label that opts an issue into automation | | ||
| | `team` | Linear team key to scope the poller to (omit for all teams) | | ||
| | `repo` | Local path where worktrees are created | | ||
| | `repoUrl` | Remote URL — used if `repo` doesn't exist locally | | ||
| | `testCommand` | Run inside the worktree after implementation | | ||
| | `statusMap.inProgress` | Linear state name to claim issues into | | ||
| | `statusMap.inReview` | Linear state name to set when PR is ready | | ||
| | `maxConcurrentIssues` | Cap on workers spawned per poll | | ||
|
|
||
| ## Permissions (required for background runs) | ||
|
|
||
| The poller fires via `RemoteTrigger` with no human in the loop, so every tool the workers call must be pre-approved — otherwise the background agent stalls on permission prompts that nobody answers. | ||
|
|
||
| The plugin ships a recommended allowlist at `plugins/linear-triage/settings.json` covering the Linear MCP tools, broad `Bash` access, and file tools. Bash is allowed broadly because the worker has to run arbitrary shell commands to explore the repo, implement fixes, and run tests — narrow patterns can't cover an autonomous coding agent. This is the same trust posture as running Claude Code interactively; only enable this for repos you'd be comfortable letting Claude Code modify on its own. | ||
|
|
||
| Merge it into your settings before kicking off background runs: | ||
|
|
||
| ```bash | ||
| # Option A — copy into project settings (scoped to one repo) | ||
| cp ~/Claude/linear-solutions/plugins/linear-triage/settings.json <your-repo>/.claude/settings.local.json | ||
|
|
||
| # Option B — merge into user settings (applies everywhere) | ||
| $EDITOR ~/.claude/settings.json # add the entries from the plugin's settings.json under permissions.allow | ||
| ``` | ||
|
|
||
| If your `testCommand` isn't one of the common runners listed (npm/pnpm/yarn/pytest/cargo/go), add it to the allowlist. After editing, restart your Claude Code session. | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| **Poller isn't firing every 15 minutes** | ||
| Check that the `RemoteTrigger` was registered: ask Claude Code to "list my remote triggers" or re-run `/linear-triage-setup`. Triggers only fire while you have a Claude Code session that can receive them — confirm your session is running. | ||
|
|
||
| **"Claude Code" label not found / can't claim issues** | ||
| Re-run `/linear-triage-setup` to recreate the label. Verify your Linear MCP connection has write scope (`save_issue`, `create_issue_label`). | ||
|
|
||
| **`mcp__claude_ai_Linear__*` tools unavailable** | ||
| Connect the Linear MCP server at [claude.ai/settings/connectors](https://claude.ai/settings/connectors) and restart Claude Code. The plugin cannot read or update issues without it. | ||
|
|
||
| **Worker fails on `gh pr create`** | ||
| Run `gh auth status` and `gh auth refresh -s repo` to ensure the CLI has repo scope. The plugin opens PRs as the authenticated `gh` user. | ||
|
|
||
| **Tests fail and the issue is labeled `needs-human`** | ||
| Expected behavior — inspect the draft PR, push a fix manually, then remove the label. The poller will not retry an issue that's no longer in triage. | ||
|
|
||
| ## Safety | ||
|
|
||
| - Workers only operate inside `repo/.worktrees/<issue-id>/` | ||
| - Each issue gets a fresh worktree on Linear's `gitBranchName` so the PR auto-links | ||
| - PRs always open as **draft**; tests must pass before they're marked ready | ||
| - Failing tests label the issue `needs-human` and stop — no merge attempt | ||
|
|
||
| ## License | ||
|
|
||
| MIT |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "label": "Claude Code", | ||
| "team": "YOUR_TEAM_KEY", | ||
| "branchPrefix": "claude", | ||
| "repo": "/absolute/path/to/your/repo", | ||
| "repoUrl": "https://github.com/YourOrg/your-repo", | ||
| "testCommand": "npm test", | ||
| "statusMap": { | ||
| "inProgress": "In Progress", | ||
| "inReview": "In Review" | ||
| }, | ||
| "maxConcurrentIssues": 3 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Linear Triage Plugin — Architecture | ||
|
|
||
| How the three skills coordinate with the Linear MCP server and local `gh`/`git`. | ||
|
|
||
| ``` | ||
| ┌─────────────────────────┐ | ||
| │ You (Linear user) │ | ||
| │ add "Claude Code" label│ | ||
| │ to a triage issue │ | ||
| └────────────┬────────────┘ | ||
| │ | ||
| ▼ | ||
| ┌──────────────────────────────────────────────────────────────────────┐ | ||
| │ LINEAR (cloud) │ | ||
| │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ | ||
| │ │ Triage queue │───▶│ "Claude Code"│ │ Issue comments & │ │ | ||
| │ │ │ │ labeled issue│ │ state transitions │ │ | ||
| │ └──────────────┘ └──────────────┘ └──────────▲───────────┘ │ | ||
| └─────────────────▲──────────────▲────────────────────│────────────────┘ | ||
| │ │ │ | ||
| list_issues│ save_issue │ save_comment │ | ||
| get_issue │ get_user │ create_label │ | ||
| │ │ │ | ||
| ┌─────┴──────────────┴────────────────────┴──────┐ | ||
| │ Linear MCP Server │ | ||
| │ (mcp__claude_ai_Linear__*) │ | ||
| └─────▲──────────────▲────────────────────▲──────┘ | ||
| │ │ │ | ||
| ─ ─ ─ ─ ─ ─ ─ ─┼─ ─ ─ ─ ─ ─ ─ ┼─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼─ ─ ─ ─ | ||
| │ │ │ | ||
| ┌──────────┴────┐ ┌──────┴───────┐ ┌────────┴─────────┐ | ||
| │ SKILL 1 │ │ SKILL 2 │ │ SKILL 3 │ | ||
| │ triage-setup │ │ triage-poller│ │ issue-worker │ | ||
| │ (one-time) │ │ (every 15m) │ │ (per issue) │ | ||
| ├───────────────┤ ├──────────────┤ ├──────────────────┤ | ||
| │• check gh/git │ │• list_issues │ │• get_issue │ | ||
| │• create label │ │ (label=CC, │ │• save_comment │ | ||
| │• register │ │ state= │ │ (stage updates) │ | ||
| │ RemoteTrigger│ │ triage) │ │• save_issue │ | ||
| │ cron */15 │ │• save_issue │ │ (state→InReview)│ | ||
| │ │ │ →InProgress │ │• get_user │ | ||
| │ │ │• save_comment│ │ (assignee @) │ | ||
| │ │ │ "picked up" │ │ │ | ||
| │ │ │• spawn worker│──▶│ (one agent per │ | ||
| │ │ │ per issue │ │ issue, parallel)│ | ||
| └───────┬───────┘ └──────┬───────┘ └────────┬─────────┘ | ||
| │ ▲ │ | ||
| │ │ cron fires │ git + gh | ||
| │ │ │ (local) | ||
| ▼ │ ▼ | ||
| ┌──────────────────────────────┐ ┌──────────────────────┐ | ||
| │ Claude Code RemoteTrigger │ │ Local repo worktree │ | ||
| │ */15 * * * * │ │ .worktrees/<id>/ │ | ||
| │ → /linear-triage-poller │ │ branch=gitBranchName│ | ||
| └──────────────────────────────┘ │ → gh pr create │ | ||
| │ --draft │ | ||
| │ → npm test │ | ||
| │ → gh pr ready │ | ||
| └──────────┬───────────┘ | ||
| │ | ||
| ▼ | ||
| ┌───────────────┐ | ||
| │ GitHub PR │ | ||
| │ (auto-linked │ | ||
| │ to issue) │ | ||
| └───────────────┘ | ||
| ``` | ||
|
|
||
| ## Reading it left-to-right | ||
|
|
||
| - **Skill 1 (`linear-triage-setup`)** — runs once. Talks to Linear MCP to create the label, talks to the Claude Code runtime to register the cron. | ||
| - **Skill 2 (`linear-triage-poller`)** — runs on the cron. Queries Linear for labeled triage issues, claims each one (state change + intake comment), then **spawns Skill 3 as a sub-agent per issue** (parallel). | ||
| - **Skill 3 (`linear-issue-worker`)** — runs once per issue. Reads the full issue from Linear, does all the local `git`/`gh`/test work in a worktree, and posts status comments back to Linear at every stage. Final step flips the issue to "In Review" and pings the assignee. | ||
|
|
||
| ## Key invariants | ||
|
|
||
| - **Linear is the queue and the status board.** All three skills share the Linear MCP server: it's how the plugin reads work (labeled triage issues) and reports progress (comments, state transitions). | ||
| - **`gh` and `git` only appear in Skill 3.** Setup and the poller never touch code or GitHub — they only orchestrate. | ||
| - **One worker per issue, isolated in a worktree.** Concurrent issues never collide because each gets its own `.worktrees/<issue-id>/` directory on Linear's `gitBranchName`. | ||
| - **The cron is the only thing that makes it "autonomous".** Without `RemoteTrigger`, the plugin is still usable — you'd just invoke `/linear-triage-poller` manually. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "mcp__claude_ai_Linear__get_issue", | ||
| "mcp__claude_ai_Linear__list_issues", | ||
| "mcp__claude_ai_Linear__save_issue", | ||
| "mcp__claude_ai_Linear__save_comment", | ||
| "mcp__claude_ai_Linear__create_issue_label", | ||
| "mcp__claude_ai_Linear__list_issue_labels", | ||
| "mcp__claude_ai_Linear__list_issue_statuses", | ||
| "mcp__claude_ai_Linear__list_teams", | ||
| "mcp__claude_ai_Linear__get_user", | ||
| "Bash", | ||
| "Task", | ||
| "Agent", | ||
| "Read", | ||
| "Edit", | ||
| "Write", | ||
| "Glob", | ||
| "Grep" | ||
| ] | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bare
Bashin the allow-list grants unattended arbitrary command execution(Critical)
settings.jsonline 14 allowsBashunconditionally. When the user merges this file into.claude/settings.local.json(the documented setup path), every bash command the worker or poller constructs — including thegit clone [repoUrl]and[testCommand]expansions that interpolate values from the Linear issue description — runs without a permission prompt. A malicious or misconfigured Linear issue could inject shell commands throughgitBranchName,repoUrl, ortestCommand. The worker shells out with these values verbatim (Stage 3git worktree add, Stage 6[testCommand] 2>&1). The minimum fix is to restrict theBashallow pattern to specific safe commands (e.g.,Bash(git *),Bash(gh *),Bash(npm test)) rather than a wildcard.