linear-sync
Move the Linear issues linked to the current branch through their workflow
states. This skill is the single source of truth for how issues are
transitioned: resolving the live state IDs, extracting issue IDs from a branch
name, and the per-state transition rules. Callers decide when and whether
to fire it; the mechanics live here once so the rules don't drift across the
ship flow, branch cleanup, and the start-of-work transition.
Configuration
Two knobs live in
beside this file. Read it at the
start of a run and use its values throughout. Edit your copied
to
match the consuming repo:
| Key | Meaning | Default |
|---|
| Linear team name used to resolve the live state IDs. Use the name, not the key — the key is renamed over time but the name is stable. | |
| Team-key prefixes that may appear in branch names. The issue-ID regex is built from these. | |
A neutral
ships alongside it as a
template — copy it over
and fill in your values, or edit
directly.
Usage modes
is passed through
(the agent reads it), matching the
"preview, change nothing" convention used across the other skills.
Normal — resolve state IDs, extract the branch's issue IDs, apply the
transition, and report what moved:
Dry run — resolve state IDs and each issue's current state, report the
intended transition (or skip reason) per issue, and exit
without any
call:
Under
the resolve + read steps still run (they are read-only), so the
preview is accurate; only the
write in the transition step is
skipped. End the report with
DRY RUN — no issues were changed.
Resolving the target state (do this once per run)
Call
mcp__linear-server__list_issue_statuses
with
once to fetch the team's live workflow states. Each carries a stable
(
/
/
/
/
/
), a
display
, an
, and a
.
Resolve the target by , not by display name. Display names are
customisable — a consumer may rename
→
or
→
— so matching the literal name silently fails to find the state (the
biggest correctness gap for adopters). Map each transition to a concrete state:
- In Progress → the state named "In Progress" (case-insensitive);
else the earliest by .
- In Review → the state whose name matches "In Review" / "Review";
else, when there are ≥2 states, the latest by ;
else the In Progress state.
- Done → the state named "Done"; else the earliest
by .
covers both In Progress and In Review, so the name match (then
)
is what separates them; a team with a single
state resolves both targets
to it. Use the resolved
in the
call.
Pass the team name, not the key. Linear state IDs are per-team, and a
workspace's team can be renamed over its lifetime (e.g. CAT → WTF → AKW → ASW),
so a hardcoded key goes stale. The team
name (
) does not move.
This is the canonical gotcha for adopters — resolve by name, every run.
Extracting issue IDs from the branch
Build the issue-ID regex
deterministically — mirror the canonical, tested
in the repo-root
,
which
copies into each consuming bundle (ADR-0004) and which
exists for exactly this job:
- Escape regex metacharacters in each key (a configured key such as
would otherwise throw or silently widen the match).
- Group the alternation. Wrap the keys in whenever there is more than
one, so the binds to the whole alternation: . The naive
join is wrong — it parses as or , matching a
bare and missing . A single key needs no wrapper: .
- Guard the empty case. With no configured keys, match nothing — never build
an empty alternation (it would match the empty string before every
and inject bogus IDs like ).
Match the result (with the
flag) against the
upper-cased branch name —
branches like
carry the key in lower case, and a flow such as
produces upper-case branch names like
. Keeping the
legacy keys means leftover branches from before a team-key rename are still
recognised. Deduplicate the matches. Bogus or malformed IDs simply error on lookup
and are skipped with a warning — no separate validation pass.
When a caller already has an
list to hand (e.g. a changelog step emits
one), use that instead of re-extracting.
Transition rules
For each issue ID, call
mcp__linear-server__get_issue
to read its current state
(use its
, not its display name). Decide using the
progression order of
state types:
text
triage < backlog < unstarted < started < completed
(which also covers a
state) is terminal and sits outside the
line. Within
, order by
, so In Progress precedes In Review. All
transitions are
idempotent: apply only when the current state is
earlier in
the progression than the resolved target;
skip silently when the issue is
already
at or past it, or when its current type is
or
(terminal states are never advanced automatically).
- In Progress (fired when starting work on an issue) — apply from /
/ ; skip from any , , or .
- In Review (fired on PR open/update inside a ship flow) — apply from /
/ , or from a state earlier by than
the resolved In Review (e.g. In Progress); skip once at or past In Review, or
/ .
- Done (fired on branch cleanup) — apply from / /
/ ; skip from / .
Apply a transition with
mcp__linear-server__save_issue
using the
resolved state
— it is unambiguous across renames. The display-name form
works only when the consumer kept Linear's default state names.
Under , skip this call. Still read each issue's
current state with
and decide whether it
would transition, but
report the intended move (e.g.
A-7: Todo → In Progress (would apply)
/
A-9: In Review (would skip — already at/past target)
) instead of writing it.
is the Linear API's own US spelling — keep it as-is when referenced
in code or config.
Caller responsibilities (when / whether to fire)
The skill owns the mechanics; each caller owns the policy:
- Start of work — transition to when work begins on an issue
(unless already In Progress or further along). Run automatically; no prompt.
- Ship flow (PR open/update) — transition linked issues to
automatically after the PR is created or updated. No prompt.
- Branch cleanup — transition orphaned issues to only after explicit
confirmation, default no. Linear's GitHub integration normally handles the
transition on PR merge, so this prompt exists only for the rare case
where the integration didn't fire (e.g. the issue ID was added after merge).
Standalone vs inside a caller
- Standalone — resolve the target state, extract the branch's issue IDs,
apply the transition, and report which issues moved and which were skipped. The
default target is In Progress (the start-of-work transition that has no
other home).
- Inside a caller — the caller supplies the target (and often the
list) and decides whether to prompt; the mechanics above are unchanged.
Implementation
No supporting scripts — the skill drives the Linear MCP tools directly
(
,
,
). The only repo-specific inputs
are the team name and the issue-ID prefixes, both read from
.