To Linear
Turn the current conversation into a properly-structured Linear ticket, filed on the right team
with the right template. Workspace, team, labels and states are all resolved at run time, so the
same skill works from any repo against any Linear workspace with no per-repo edit.
Invocation
/to-linear [type] [notes] — or just describe the intent ("file this as a bug").
- No type given → infer from conversation context (see below).
- Explicit type given → always wins over inference.
- Trailing notes → free text to fold in (extra context, a title hint, scope notes). Optional.
Resolving the type
Three types, deliberately. There is no separate "improvement" type: tech debt, refactors and
hardening are stories, filed with the story template.
| Type |
Aliases (case-insensitive) |
bug |
bug, bugfix, bug report, defect, regression |
story |
story, user story, us, feature, improvement, tech debt, chore, refactor, hardening |
epic |
epic |
Inferring when no type is given:
- Something is broken now — a reproduced defect, an error, a regression →
bug
- Anything else that should exist or should change: a requested capability, tech debt, a missing
guard, a hardening task with no user-visible break →
story
- A body of work spanning multiple stories, needing a target date →
epic
The line is "is it broken for someone today?" — if not, it is a story, however
engineering-flavoured it sounds. If inference is genuinely ambiguous between two types, ask
rather than guess.
Type → template + destination
| Type |
Template |
Becomes |
Type label |
bug |
templates/bug.md |
Linear issue |
Bug |
story |
templates/story.md |
Linear issue |
Feature |
epic |
templates/epic.md |
Linear project on the resolved team |
— |
Templates live in templates/ next to this file. Read the one you need with the Read tool and
fill its sections — they are plain markdown, which is exactly what Linear descriptions accept.
Do not reproduce a template from memory; read the file.
Keep every section the template defines. If a section has no information yet, write an honest
placeholder ("TBD — needs repro steps") rather than inventing detail. Add extra ## sections
freely when the ticket needs them. For a story that is tech debt rather than a user-facing
capability, the "As a … I want …" line is often a poor fit — replace it with a one-line statement
of what should change and why, and keep every other section.
Tooling: the Linear MCP
All reads and writes go through the Linear MCP tools (save_issue, save_project,
list_teams, list_issue_labels, list_issue_statuses, …). Load them with
ToolSearch if they are deferred.
save_issue both creates and updates, and id is the switch: omit it and the call creates a
new issue, pass it and the call updates that one. There is no separate create tool and no
separate update tool — the same holds for save_project and save_comment. A create with a stray id is an
overwrite of someone else's ticket, so the omission is load-bearing, not cosmetic.
If the only Linear tools visible are authenticate/complete_authentication, the MCP is not
authenticated — ask the user to run /mcp and select the Linear connector, then retry. Do not
fall back to hand-rolled GraphQL calls; there is no shared API key.
Step 1: Resolve the destination (before drafting anything)
Workspace — resolve it, never hardcode it, and always show it before writing. The Linear MCP
takes no workspace parameter: the connector's token binds the session to exactly one workspace,
so the workspace is not something you select, it is something you read back and confirm. Resolve
in this order:
$LINEAR_WORKSPACE — printenv LINEAR_WORKSPACE. Set it (in .envrc, alongside
$LINEAR_TEAM) in any checkout that must only ever file into one workspace. It is a guard, not
a selector: if the connector is bound to a different workspace, that is a mismatch you STOP on,
because there is no parameter with which to redirect the write.
- Otherwise read it back from the connector — the workspace/organization on the
list_teams
result, or the host path of any returned issue url (https://linear.app/<workspace>/…).
Name the resolved workspace in the pre-filing summary, every time, and name the team beside it.
Multiple Linear connectors can be configured, and the failure this prevents is filing a correct
ticket into the wrong company's tracker — which no read-back after the fact undoes.
Team varies per repo. Resolve in this order and stop at the first hit:
- The invocation says so — "file this on NBS", or a pasted issue/project URL. Explicit wins.
$LINEAR_TEAM — printenv LINEAR_TEAM. This is the per-checkout pin; repos set it in
.envrc (export LINEAR_TEAM=NBS).
- The repo's own docs — the ticket-key convention is usually written down:
grep -rEn '\b[A-Z]{2,6}-[0-9]+\b|linear\.app' CLAUDE.md AGENTS.md README.md .claude/ docs/ 2>/dev/null | head -30.
A branch rule like NBS-123-short-description names the key outright. Recent branches work
too: git branch -a --format='%(refname:short)' | grep -oE '[A-Z]{2,6}-[0-9]+' | sort -u.
- Ask —
list_teams. Exactly one team ⇒ use it. Several ⇒ ask which, showing the keys.
Do not pick the alphabetically-first one, and do not assume the company-wide team is the
engineering one — workspaces commonly carry both, and filing into the company-wide team puts the
ticket in front of the wrong people.
Then, on the resolved team, read what actually exists before writing:
list_issue_statuses — find the status whose type is backlog. Match on the type, never
on the name: teams disagree (Backlog, Todo, To Do) and hardcoding a name silently fails.
list_issue_labels — confirm the type label exists (see below).
Step 2: Labels — one, usually
- Set exactly one type label:
Bug for a bug, Feature for a story. Match
case-insensitively against what list_issue_labels returned. If neither exists on this team,
file without a label and say so in your closing summary — never create a label.
- No topical labels by default. Add one only when the user explicitly names it, and only if
it already exists. Never invent one.
- Never set an import/migration marker label (e.g.
Migrated) — those mean "came from the old
tracker", not "belongs to this ticket".
Step 3: Filing an issue (bug / story)
Call save_issue with no id (that is what makes it a create) and:
team: the team resolved in Step 1
title: specific and one line — it carries the finding, not the area. "Proactive messages
never receive platform_name, so the brand redactor no-ops" beats "Redactor bug". Keep it short:
Linear derives branchName from it, and the identifier in that branch is what links the PR
back (merging a linked PR is what closes the issue on most teams). Follow the repo's own git
rules if it has one.
description: the filled template markdown
labels: the single type label from Step 2
state: the type: backlog status resolved in Step 1
Rules:
- The status is backlog and nothing else. Never file straight into an in-progress or review
state — triage moves tickets, this skill does not.
- Only set a project when the work belongs to a real epic-project. Standalone bugs and
stories get no project. A project means "a finite body of work with a target date", not a
category bucket.
- Set
priority when severity is clear from the conversation; otherwise leave it off rather
than guessing.
- If a field is not configured on the team (estimates and cycles are off for some), leave it
alone rather than trying to set it.
Return the issue URL and identifier (e.g. NBS-231) from the response.
Step 4: Filing an epic (a project)
Call save_project with no id and:
team: the team resolved in Step 1
name: the epic title
summary/description (the short field): the one-line goal, plain text, shown in list views
content: the full markdown body from templates/epic.md
targetDate: if one is known
Check whether the workspace uses initiatives before linking to one — it currently does not. Then
file the child stories as issues with the new project set on each, each one following Step 3.
Notes
- This skill creates. Updating an existing issue is the same
save_issue with an id; moving status afterwards
is not this skill's job.
- Linear writes are outward-facing. Default to creating directly, but if the user is clearly
mid-thought or the content is thin, show the draft first.
- Making an existing ticket ready to work on is
/linear-groom-ticket, not this skill —
it analyses one filed ticket, redrafts it against the team template, and applies the result
after you approve. This skill only creates. /to-linear then /linear-groom-ticket is the
normal path for a ticket filed from a thin conversation.
- Handing a ticket to an autonomous pipeline is
/spec-to-symphony, not this skill — it has its
own arming, branch and spec-path contract, and drives a Symphony pipeline rather than just filing.
It also continues a ticket rather than creating one, so this skill is its upstream: /to-linear
files, /linear-groom-ticket grooms, /linear-spec-ticket attaches the spec, /spec-to-symphony
pushes it to the remote and arms.
1---2name: to-linear3description: Create a properly-templated Linear issue or epic-project (bug, story, epic) from the current conversation, on whichever team this repo files to. Use this WHENEVER the user wants to write, file, log, or create a ticket/story/bug/epic. Triggers on: write a bug ticket, file this as a bug, log a defect, create a user story, file tech debt, turn this into an epic, file a ticket, to linear, to-linear.4---56# To Linear78Turn the current conversation into a properly-structured Linear ticket, filed on the right team9with the right template. **Workspace, team, labels and states are all resolved at run time**, so the10same skill works from any repo against any Linear workspace with no per-repo edit.1112## Invocation1314`/to-linear [type] [notes]` — or just describe the intent ("file this as a bug").1516- **No type given** → infer from conversation context (see below).17- **Explicit type given** → always wins over inference.18- **Trailing notes** → free text to fold in (extra context, a title hint, scope notes). Optional.1920## Resolving the type2122Three types, deliberately. There is no separate "improvement" type: **tech debt, refactors and23hardening are stories**, filed with the story template.2425| Type | Aliases (case-insensitive) |26|---|---|27| `bug` | bug, bugfix, bug report, defect, regression |28| `story` | story, user story, us, feature, improvement, tech debt, chore, refactor, hardening |29| `epic` | epic |3031**Inferring** when no type is given:3233- Something is broken *now* — a reproduced defect, an error, a regression → `bug`34- Anything else that should exist or should change: a requested capability, tech debt, a missing35 guard, a hardening task with no user-visible break → `story`36- A body of work spanning multiple stories, needing a target date → `epic`3738The line is **"is it broken for someone today?"** — if not, it is a `story`, however39engineering-flavoured it sounds. If inference is genuinely ambiguous between two types, ask40rather than guess.4142## Type → template + destination4344| Type | Template | Becomes | Type label |45|---|---|---|---|46| `bug` | `templates/bug.md` | Linear issue | `Bug` |47| `story` | `templates/story.md` | Linear issue | `Feature` |48| `epic` | `templates/epic.md` | Linear **project** on the resolved team | — |4950Templates live in `templates/` next to this file. Read the one you need with the Read tool and51fill its sections — they are plain markdown, which is exactly what Linear descriptions accept.52Do not reproduce a template from memory; read the file.5354Keep every section the template defines. If a section has no information yet, write an honest55placeholder ("TBD — needs repro steps") rather than inventing detail. Add extra `##` sections56freely when the ticket needs them. For a story that is tech debt rather than a user-facing57capability, the "As a … I want …" line is often a poor fit — replace it with a one-line statement58of what should change and why, and keep every other section.5960## Tooling: the Linear MCP6162All reads and writes go through the **Linear MCP** tools (`save_issue`, `save_project`,63`list_teams`, `list_issue_labels`, `list_issue_statuses`, …). Load them with64ToolSearch if they are deferred.6566**`save_issue` both creates and updates**, and `id` is the switch: omit it and the call creates a67new issue, pass it and the call updates that one. There is no separate create tool and no68separate update tool — the same holds for `save_project` and `save_comment`. A create with a stray `id` is an69overwrite of someone else's ticket, so the omission is load-bearing, not cosmetic.7071If the only Linear tools visible are `authenticate`/`complete_authentication`, the MCP is not72authenticated — ask the user to run `/mcp` and select the Linear connector, then retry. Do not73fall back to hand-rolled GraphQL calls; there is no shared API key.7475## Step 1: Resolve the destination (before drafting anything)7677**Workspace — resolve it, never hardcode it, and always show it before writing.** The Linear MCP78takes **no workspace parameter**: the connector's token binds the session to exactly one workspace,79so the workspace is not something you select, it is something you *read back and confirm*. Resolve80in this order:81821. **`$LINEAR_WORKSPACE`** — `printenv LINEAR_WORKSPACE`. Set it (in `.envrc`, alongside83 `$LINEAR_TEAM`) in any checkout that must only ever file into one workspace. It is a **guard, not84 a selector**: if the connector is bound to a different workspace, that is a mismatch you STOP on,85 because there is no parameter with which to redirect the write.862. **Otherwise read it back** from the connector — the workspace/organization on the `list_teams`87 result, or the host path of any returned issue `url` (`https://linear.app/<workspace>/…`).8889**Name the resolved workspace in the pre-filing summary, every time**, and name the team beside it.90Multiple Linear connectors can be configured, and the failure this prevents is filing a correct91ticket into the wrong company's tracker — which no read-back after the fact undoes.9293**Team** varies per repo. Resolve in this order and stop at the first hit:94951. **The invocation says so** — "file this on NBS", or a pasted issue/project URL. Explicit wins.962. **`$LINEAR_TEAM`** — `printenv LINEAR_TEAM`. This is the per-checkout pin; repos set it in97 `.envrc` (`export LINEAR_TEAM=NBS`).983. **The repo's own docs** — the ticket-key convention is usually written down:99 `grep -rEn '\b[A-Z]{2,6}-[0-9]+\b|linear\.app' CLAUDE.md AGENTS.md README.md .claude/ docs/ 2>/dev/null | head -30`.100 A branch rule like `NBS-123-short-description` names the key outright. Recent branches work101 too: `git branch -a --format='%(refname:short)' | grep -oE '[A-Z]{2,6}-[0-9]+' | sort -u`.1024. **Ask** — `list_teams`. Exactly one team ⇒ use it. Several ⇒ ask which, showing the keys.103 Do not pick the alphabetically-first one, and do not assume the company-wide team is the104 engineering one — workspaces commonly carry both, and filing into the company-wide team puts the105 ticket in front of the wrong people.106107Then, on the resolved team, read what actually exists before writing:108109- `list_issue_statuses` — find the status whose **`type` is `backlog`**. Match on the type, never110 on the name: teams disagree (`Backlog`, `Todo`, `To Do`) and hardcoding a name silently fails.111- `list_issue_labels` — confirm the type label exists (see below).112113## Step 2: Labels — one, usually114115- **Set exactly one type label**: `Bug` for a bug, `Feature` for a story. Match116 case-insensitively against what `list_issue_labels` returned. If neither exists on this team,117 file without a label and say so in your closing summary — **never create a label.**118- **No topical labels by default.** Add one only when the user explicitly names it, and only if119 it already exists. Never invent one.120- Never set an import/migration marker label (e.g. `Migrated`) — those mean "came from the old121 tracker", not "belongs to this ticket".122123## Step 3: Filing an issue (bug / story)124125Call `save_issue` **with no `id`** (that is what makes it a create) and:126127- `team`: the team resolved in Step 1128- `title`: specific and one line — it carries the finding, not the area. "Proactive messages129 never receive platform_name, so the brand redactor no-ops" beats "Redactor bug". Keep it short:130 Linear derives `branchName` from it, and the identifier in that branch is what links the PR131 back (merging a linked PR is what closes the issue on most teams). Follow the repo's own git132 rules if it has one.133- `description`: the filled template markdown134- `labels`: the single type label from Step 2135- `state`: the `type: backlog` status resolved in Step 1136137Rules:138139- **The status is backlog and nothing else.** Never file straight into an in-progress or review140 state — triage moves tickets, this skill does not.141- **Only set a project when the work belongs to a real epic-project.** Standalone bugs and142 stories get no project. A project means "a finite body of work with a target date", not a143 category bucket.144- Set `priority` when severity is clear from the conversation; otherwise leave it off rather145 than guessing.146- If a field is not configured on the team (estimates and cycles are off for some), leave it147 alone rather than trying to set it.148149Return the issue URL and identifier (e.g. `NBS-231`) from the response.150151## Step 4: Filing an epic (a project)152153Call `save_project` **with no `id`** and:154155- `team`: the team resolved in Step 1156- `name`: the epic title157- `summary`/`description` (the short field): the one-line goal, plain text, shown in list views158- `content`: the full markdown body from `templates/epic.md`159- `targetDate`: if one is known160161Check whether the workspace uses initiatives before linking to one — it currently does not. Then162file the child stories as issues with the new project set on each, each one following Step 3.163164## Notes165166- This skill **creates**. Updating an existing issue is the same `save_issue` with an `id`; moving status afterwards167 is not this skill's job.168- Linear writes are outward-facing. Default to creating directly, but if the user is clearly169 mid-thought or the content is thin, show the draft first.170- **Making an existing ticket ready to work on is `/linear-groom-ticket`**, not this skill —171 it analyses one filed ticket, redrafts it against the team template, and applies the result172 after you approve. This skill only creates. `/to-linear` then `/linear-groom-ticket` is the173 normal path for a ticket filed from a thin conversation.174- Handing a ticket to an autonomous pipeline is `/spec-to-symphony`, not this skill — it has its175 own arming, branch and spec-path contract, and drives a Symphony pipeline rather than just filing.176 It also **continues** a ticket rather than creating one, so this skill is its upstream: `/to-linear`177 files, `/linear-groom-ticket` grooms, `/linear-spec-ticket` attaches the spec, `/spec-to-symphony`178 pushes it to the remote and arms.