Awesome-omni-skill linear-sync
This skill should be used when the session-start hook injects Linear context (e.g., "[Linear/..." or "[LINEAR-SETUP]" or "[LINEAR-DIGEST]"), when the prompt-check hook injects "[Linear/<workspace>] Issue(s) referenced:", when the commit guard hook blocks a command for missing an issue ID or injects "[CROSS-ISSUE-COMMITS]", when the user mentions Linear issues (e.g., "ENG-123", "OPL-456"), when creating commits/branches/PRs in a Linear-linked repo, when the user asks about Linear workflow or issue tracking, or when working in any repository that has a .claude/linear-sync.json config file. Provides behavioral rules for Linear-GitHub sync workflow orchestration.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/ai-agents/linear-sync" ~/.claude/skills/diegosouzapw-awesome-omni-skill-linear-sync && rm -rf "$T"
skills/ai-agents/linear-sync/SKILL.mdLinear Sync Workflow
This system keeps Linear and GitHub in sync automatically. Linear API queries go through the
linear-sync subagent (foreground), while simple mutations use linear-api.sh directly via Bash.
Session Start Behavior
At the start of every session, the
linear-session-start hook fires and checks the repo:
- Linked repo: The hook injects config context (workspace, project, team, label, formats, last_issue if any, stale branch warnings, and pre-fetched digest if due). Follow the session kickoff directive below.
- Opted-out repo (
): The hook is silent. Do nothing related to Linear.workspace: "none" - Unregistered repo: The hook injects a
directive. Follow the setup wizard rules below. If a[LINEAR-SETUP]
is found, the hook also injects.linear-sync-template.json
with defaults to pre-fill.[LINEAR-TEMPLATE] - Broken workspace reference: The hook injects a
directive to reconfigure or opt out.[LINEAR-SETUP]
Session Kickoff (Linked Repos)
When the hook injects config context for a linked repo:
Step 1: Digest and stale branches
- Notification digest (if
data is present in hook context): The session-start hook pre-fetches your active issues inline. Show the digest briefly (1 line) before asking what to work on.[LINEAR-DIGEST] - GitHub issue sync: Runs automatically via PostToolUse hook after
orgit push
— no action needed at session start.gh pr create - Stale branches (if
is present): Show a brief warning. Example: "Heads up: branch[STALE-BRANCHES]
has had no commits in 7 days."dan/ENG-100-old-feature
Step 2: Resume or ask
If the hook context includes
last_issue: <ISSUE_ID>, offer to resume that issue first:
Use AskUserQuestion: "What are you working on today in <repo>?"
- "Resume <ISSUE_ID>" — Delegate to
subagent (foreground) to fetch the issue summary. After fetching, savelinear-sync
for this repo (direct Bash — see Execution Model). Check for blockers in the response and warn if any.last_issue - "Work on a different issue" — Delegate to
subagent (foreground) to fetch the dev's assigned in-progress issues ("Fetch My Issues" task). Present the returned list as AskUserQuestion choices, plus an option to "Enter an issue ID manually". After selection, savelinear-sync
for this repo (direct Bash). Check for blockers in the response and warn if any.last_issue - "Start something new" — Ask for a one-line description via AskUserQuestion. Check for duplicates first (see Duplicate Detection below). Then delegate to
subagent (foreground) to create a ticket in the correct project with the repo label. After creation, offer to assign to current cycle (see Cycle Assignment below). Use the returned issue ID going forward.linear-sync - "Just exploring / no ticket needed" — Proceed normally. The commit guard hook will catch untagged commits later and you can offer to create a ticket at that point.
If no
last_issue is present, skip option 1 and start with options 2-4 (renumber accordingly).
When the dev picks any issue (resume, existing, or new): If the issue is unassigned, auto-assign it directly via
linear-api.sh (see Execution Model — Direct Bash). No need to ask — if they're working on it, they should own it. Parallel optimization: Run query { viewer { id } } in the same message as the subagent fetch so the viewer ID is ready when the issue details come back.
Keep the kickoff brief and natural: "What are you working on today in <repo>?"
Setup Wizard Rules
When a
[LINEAR-SETUP] directive is injected:
- Always use AskUserQuestion for every choice. The dev never types workspace names, project IDs, or labels manually.
- Always offer "This repo doesn't use Linear" as an option. If chosen, opt the repo out via Read/Write tools (read state file, set
, write back) and move on.workspace: "none" - Use the
subagent in foreground mode to fetch real data from Linear MCP (workspaces, teams, projects, labels).linear-sync - Present fetched data as AskUserQuestion choices.
- For labels, suggest
as the default.repo:<repo-name> - Detect the GitHub org from the repo's remote URL. Include it as
in the committed config.github_org - Write
to the repo root with the shared config..claude/linear-sync.json - Write only the
reference to the local state file for credential routing. Project, team, and label live in the repo-level config.workspace - The subagent will commit and push
as part of the Link Repo task..claude/linear-sync.json - After setup, confirm in one line and proceed with the session kickoff.
Project Rules
- Multiple repos can share the same project. Include "Create a new project" as the last option.
- If the dev picks "Create a new project", ask for a name, delegate to the subagent to create it.
Enforcement Rules (Linked Repos Only)
These conventions are mechanically enforced by hooks. They apply ONLY to repos with a valid workspace link.
| Action | Requirement | Enforcement |
|---|---|---|
| Issue ID in message (e.g., ) | Hook blocks with exit 2 |
/ | Issue ID in branch name (e.g., ) | Hook blocks with exit 2 |
| Issue ID in PR title (e.g., ) | Hook blocks with exit 2 |
| Cross-issue commit check | Hook allows with advisory context |
| Issue creation | Repo label auto-applied and auto-created if missing | Subagent handles |
| Issue creation | Check for duplicates first | Subagent search + AskUserQuestion |
| Issue creation | Infer priority from keywords | AskUserQuestion if detected |
| Issue creation | Offer cycle/sprint assignment | AskUserQuestion if active cycle |
| Issue picked / resumed | Auto-assign if unassigned | Direct Bash () |
(after success) | Offer a progress comment on the Linear issue | PostToolUse hook reminder + AskUserQuestion |
(final push) | Offer a progress comment on the Linear issue | PostToolUse hook reminder + AskUserQuestion |
| Session ending / major milestone | Offer a progress comment on the Linear issue | AskUserQuestion |
Linear Comments
IMPORTANT: You MUST offer a comment at every natural stopping point. Never post automatically, but always ask.
The PostToolUse hook automatically injects a
[Linear Comment Reminder] after git push and gh pr create in linked repos. When you see this reminder, follow the comment flow below.
Natural stopping points (offer every time):
- After creating a PR — this is the most important one
- After a significant push when wrapping up a chunk of work
- When the dev says they're done or is ending the session
Smart comment drafts: Before drafting a comment, run
git log main..HEAD --oneline (or the appropriate base branch) to gather the commit history for this branch. Use the commit messages to auto-draft a concise progress summary.
Use AskUserQuestion with a draft comment:
I can add a quick update to ENG-423: "Added sliding window rate limiter (3 commits): - Redis-backed per-user rate limiting - Configurable window size and limits - 94% test coverage PR: #142" 1. Post this comment 2. Let me edit it 3. Skip
If they pick "edit", ask them what they'd like it to say. Never post without showing them exactly what will be posted first. If they skip, don't ask again until the next stopping point.
Posting comments: Use direct Bash with
linear-api.sh (see Execution Model — no subagent needed for simple mutations).
Issue Status
If the dev explicitly asks to change a status, do it. No pushback, no warnings. Use direct Bash with
linear-api.sh and confirm.
For automatic status changes, only act at these moments:
- "Start something new" — Set to In Progress on creation.
- Blocked commit recovery — Set to In Progress on creation.
Don't automatically change status beyond those two cases. The Linear <-> GitHub integration handles the rest.
Commit Message Formatting
Commits in linked repos must include an issue ID (e.g.,
ENG-123: description). Do NOT append Co-Authored-By lines to commit messages. Keep commit messages clean: just the issue ID prefix and a concise description.
Branch Naming
Auto-generate branch names from the issue ID and title. Slugify and truncate the description portion to keep the total branch name under 50 characters.
Examples:
alice/ENG-456-add-sliding-window-ratealice/CP-89-fix-auth-timeout-mobile
Keep the meaningful words, drop filler. The dev never manually names branches in a linked repo.
Blocked Commit Recovery
When a commit, branch, or PR is blocked by the hook for missing an issue ID:
- Proactively offer to create a Linear ticket. Do not just report the error.
- Use AskUserQuestion to ask for a one-line description of the work.
- Delegate to
subagent (foreground) to create the ticket.linear-sync - Retry the original command with the new issue ID inserted.
- Do not make the dev start over or re-type their commit message.
Duplicate Issue Detection
Before creating any new issue:
- Delegate to
subagent (foreground) with the "Search Issues" task.linear-sync - If duplicates found, present them via AskUserQuestion with option to use existing or create anyway.
- If no duplicates, proceed with creation normally.
Blocking Issue Warnings
When an issue is fetched, check for blocker warnings. If blockers are present:
- Show the warning: "Heads up: this issue is blocked by ENG-100."
- Do NOT prevent the dev from working on it. It's advisory.
- Use AskUserQuestion: "Work on it anyway? / Switch to the blocking issue instead? / Pick a different issue?"
PR Auto-Description
When
gh pr create is about to run and you have issue context:
- Draft the PR body using issue title, description, and acceptance criteria.
- Structure the PR body as:
## Summary <1-2 sentence summary from issue context> ## Linear Issue <ISSUE_ID>: <title> ## Changes <bullet points from `git log main..HEAD --oneline`> ## Acceptance Criteria <from issue description, or "See Linear issue" if none> - The issue ID must still appear in the
.--title
Cycle/Sprint Auto-Assignment
When creating a new issue:
- After creation, delegate to
subagent (foreground) to fetch the active cycle for the team.linear-sync - If an active cycle exists, use AskUserQuestion to offer adding the issue to it.
- If no active cycle, skip silently.
- To add: use direct Bash with
(no subagent needed).linear-api.sh
Priority Inference
Scan descriptions for urgency signals:
| Keywords | Suggested Priority |
|---|---|
, , , , , , , | 1 (Urgent) |
, , , , , | 2 (High) |
, , , , | 3 (Medium) |
, , , , , , , , | 4 (Low) |
If keywords detected, use AskUserQuestion to confirm. If no keywords match, create without priority.
Cross-Issue Commit Warning
If the commit guard hook injects a
[CROSS-ISSUE-COMMITS] advisory on push, show it: "Advisory: This branch has commits referencing different issues (ENG-123, ENG-456). This is usually fine for related work, but you may want to split into separate branches if the work is unrelated." This is a warning, not a block.
Closed Issues Without PRs
If the session-start digest mentions closed issues without linked PRs, surface it briefly: "Note: ENG-123 was marked Done but has no linked PR." This is informational only.
Team Config Templates
If a
.linear-sync-template.json exists in the repo root and the hook injects [LINEAR-TEMPLATE], use its values as defaults during the setup wizard. Present them for confirmation: "Found a team config template. Use these defaults? Workspace: X, Project: Y, Team: Z, Label: W". The dev can override any value.
Execution Model
Operations use three tiers to avoid context bloat and work around background subagent permission limitations:
Hooks (zero context cost)
- GitHub sync — PostToolUse hook runs
aftersync-github-issues.sh
/git push
in linked repos. Injectsgh pr create
with the result.[Linear Comment Reminder] - Notification digest — SessionStart hook pre-fetches active issues inline via
. Result appears inlinear-api.sh
context.[LINEAR-DIGEST]
Foreground subagent (linear-sync
)
linear-syncUse for operations that return data the main agent needs to present:
- Fetch Issue Summary — issue details + blocker warnings
- Fetch My Issues — assigned in-progress issues list
- Create Issue — creates ticket, returns issue ID
- Search Issues — duplicate detection before creation
- Fetch Active Cycle — cycle info for assignment offer
- Link Repo — setup wizard (one-time)
When delegating to the subagent, always include the
scripts_dir from the session-start hook context in your prompt so the subagent can find linear-api.sh. Example: "Search for duplicates in OPL project. scripts_dir: /path/to/scripts"
Direct Bash (auto-approved by PreToolUse hook)
Use
linear-api.sh at the path from the session-start hook's scripts_dir field for Linear API mutations. A PreToolUse hook auto-approves single-line bash */linear-api.sh commands (and multiline with variable assignments), so these run without permission prompts:
- Auto-assign:
thenbash linear-api.sh 'query { viewer { id } }'bash linear-api.sh 'mutation { issueUpdate(id: "...", input: { assigneeId: "..." }) { issue { id } } }' - Add to cycle:
bash linear-api.sh 'mutation { issueUpdate(id: "...", input: { cycleId: "..." }) { issue { id } } }' - Post comment: See the
escaping workaround below, then:!bash linear-api.sh "$QUERY" '{"input": {"issueId": "...", "body": "..."}}' - Status change:
bash linear-api.sh 'mutation { issueUpdate(id: "...", input: { stateId: "..." }) { issue { id } } }'
CRITICAL: !
escaping in GraphQL queries
!The Bash tool escapes
! to \! even inside single quotes, breaking GraphQL non-null types like CommentCreateInput!. Always use printf on a separate line to construct queries containing !:
QUERY=$(printf 'mutation($input: CommentCreateInput%s) { commentCreate(input: $input) { comment { id } } }' '!') bash linear-api.sh "$QUERY" '{"input": {"issueId": "...", "body": "..."}}'
Important: Put
QUERY= and bash linear-api.sh on separate lines (not chained with &&). The auto-approve hook validates each line independently and rejects && chains for security.
This applies to any query with
! (e.g., IssueCreateInput!, CommentCreateInput!, String!). Queries without ! (simple queries, issueUpdate, viewer) can use the normal single-line syntax.
Parallel execution
Always batch independent API calls in the same message. Claude can make multiple tool calls simultaneously when they don't depend on each other. Key patterns:
- Issue resume + auto-assign: Run the subagent fetch AND
in the same message. When both return, you have the issue UUID and viewer ID — fire the assign mutation immediately.query { viewer { id } } - Multiple independent mutations: If you need to assign + add to cycle + change status, batch all three mutations in one message.
- State update + API call: Read/Write to the state file can happen in parallel with an API mutation since they're independent operations.
Sequential only when dependent: A mutation that needs data from a query (e.g., assign needs viewer ID) must wait for the query to complete first. But two independent queries should always run in parallel.
Read/Write tools (no Bash needed)
Use the Read and Write tools for state file updates — no permission prompts, no subagent overhead:
- Save last_issue: Read
, update~/.claude/linear-sync/state.json
, Write backrepos.<repo>.last_issue - Opt repo out: Read state file, set
torepos.<repo>.workspace
, Write back"none"
Companion Files
This skill orchestrates components at the plugin root (path injected by session-start hook as
scripts_dir):
- Subagent:
— handles Linear API queries (foreground only)agents/linear-sync.md - Hooks:
(session init + inline digest),hooks/scripts/linear-session-start.sh
(commit/branch/PR enforcement),linear-commit-guard.sh
(issue reference injection),linear-prompt-check.sh
(GitHub sync + comment reminder)linear-post-push-sync.sh - Scripts:
(API wrapper),scripts/linear-api.sh
(GH sync)scripts/sync-github-issues.sh - State:
(credential routing, session state)~/.claude/linear-sync/state.json