Guides optimal Linear operations usage with caching, performance patterns, and error handling. Auto-activates when implementing CCPM commands that interact with Linear. Prevents usage of non-existent Linear MCP tools.
Optimizes Linear API operations with caching, performance patterns, and error handling. Auto-activates when implementing CCPM commands that interact with Linear. Prevents usage of non-existent Linear MCP tools.
/plugin marketplace add duongdev/ccpm/plugin install ccpm@duongdev-ccpm-marketplaceThis skill is limited to using the following tools:
These are the EXACT parameter names from get_server_tools. Copy exactly.
// GET ISSUE - uses "id"
mcp__agent-mcp-gateway__execute_tool({
server: "linear",
tool: "get_issue",
args: { id: "WORK-26" } // ✅ CORRECT - "id" not "issueId"
})
// UPDATE ISSUE - uses "id"
mcp__agent-mcp-gateway__execute_tool({
server: "linear",
tool: "update_issue",
args: {
id: "WORK-26", // ✅ CORRECT - "id" not "issueId"
description: "...",
state: "In Progress"
}
})
// CREATE COMMENT - uses "issueId"
mcp__agent-mcp-gateway__execute_tool({
server: "linear",
tool: "create_comment",
args: {
issueId: "WORK-26", // ✅ CORRECT - "issueId" for comments
body: "Comment text"
}
})
// LIST COMMENTS - uses "issueId"
mcp__agent-mcp-gateway__execute_tool({
server: "linear",
tool: "list_comments",
args: { issueId: "WORK-26" } // ✅ CORRECT
})
| Tool | Required Parameter | Example Args |
|---|---|---|
get_issue | id | { id: "WORK-26" } |
update_issue | id | { id: "WORK-26", ... } |
create_comment | issueId, body | { issueId: "WORK-26", body: "..." } |
list_comments | issueId | { issueId: "WORK-26" } |
create_issue | title, team | { title: "...", team: "..." } |
get_project | query | { query: "ProjectName" } |
get_team | query | { query: "TeamName" } |
get_user | query | { query: "me" } |
The Linear subagent (ccpm:linear-operations) is a dedicated MCP handler that optimizes all Linear API operations in CCPM. Rather than making direct Linear MCP calls, commands should delegate to this subagent, which provides:
Use the subagent for:
Never use the subagent for:
Different Linear MCP tools use different parameter names for issue IDs:
| Tool | Parameter | WRONG | CORRECT |
|---|---|---|---|
get_issue | id | { issueId: "X" } ❌ | { id: "X" } ✅ |
update_issue | id | { issueId: "X" } ❌ | { id: "X" } ✅ |
create_comment | issueId | { id: "X" } ❌ | { issueId: "X" } ✅ |
list_comments | issueId | { id: "X" } ❌ | { issueId: "X" } ✅ |
First-call failures are often caused by using issueId instead of id for get_issue/update_issue.
Linear MCP provides 23 tools for interacting with Linear. This is the complete, validated list.
list_commentsList comments for a specific Linear issue.
Parameters:
issueId (string, required) - The issue IDExample:
mcp__linear__list_comments({ issueId: "PSN-41" })
create_commentCreate a comment on a specific Linear issue.
Parameters:
issueId (string, required) - The issue IDbody (string, required) - The content of the comment as MarkdownparentId (string, optional) - A parent comment ID to reply toLinear's Native Collapsible Syntax:
Use +++ to create collapsible sections (starts collapsed, native Linear feature):
+++ Section Title
Content here (multi-line markdown supported)
+++
Example (simple):
mcp__linear__create_comment({
issueId: "PSN-41",
body: "## Progress Update\n\nCompleted first phase."
})
Example (with collapsible section):
mcp__linear__create_comment({
issueId: "PSN-41",
body: `🔄 **Progress Update**
Completed first phase, all tests passing
+++ 📋 Detailed Context
**Changed Files:**
- src/auth.ts
- src/tests/auth.test.ts
**Next Steps:**
- Implement phase 2
- Update documentation
+++`
})
list_cyclesRetrieve cycles for a specific Linear team.
Parameters:
teamId (string, required) - The team IDtype (string, optional) - "current", "previous", or "next"Example:
mcp__linear__list_cycles({ teamId: "team-123", type: "current" })
get_documentRetrieve a Linear document by ID or slug.
Parameters:
id (string, required) - The document ID or slugExample:
mcp__linear__get_document({ id: "doc-abc-123" })
list_documentsList documents in the user's Linear workspace.
Parameters:
limit (number, optional, max 250, default 50)before (string, optional) - An ID to end atafter (string, optional) - An ID to start fromorderBy (string, optional) - "createdAt" or "updatedAt" (default: "updatedAt")query (string, optional) - Search queryprojectId (string, optional) - Filter by project IDinitiativeId (string, optional) - Filter by initiative IDcreatorId (string, optional) - Filter by creator IDcreatedAt (string, optional) - ISO-8601 date-time or duration (e.g., "-P1D")updatedAt (string, optional) - ISO-8601 date-time or durationincludeArchived (boolean, optional, default false)Example:
mcp__linear__list_documents({ projectId: "proj-123", limit: 20 })
get_issueRetrieve detailed information about an issue by ID.
Parameters:
id (string, required) - The issue ID (e.g., "PSN-41")Example:
mcp__linear__get_issue({ id: "PSN-41" })
list_issuesList issues in the user's Linear workspace. Use "me" for assignee to get your issues.
Parameters:
limit (number, optional, max 250, default 50)before (string, optional)after (string, optional)orderBy (string, optional) - "createdAt" or "updatedAt"query (string, optional) - Search title or descriptionteam (string, optional) - Team name or IDstate (string, optional) - State name or IDcycle (string, optional) - Cycle name or IDlabel (string, optional) - Label name or IDassignee (string, optional) - User ID, name, email, or "me"delegate (string, optional) - Agent name or IDproject (string, optional) - Project name or IDparentId (string, optional) - Parent issue IDcreatedAt (string, optional) - ISO-8601 date-time or durationupdatedAt (string, optional) - ISO-8601 date-time or durationincludeArchived (boolean, optional, default true)Example:
mcp__linear__list_issues({ assignee: "me", state: "In Progress" })
create_issueCreate a new Linear issue.
Parameters:
title (string, required)team (string, required) - Team name or IDdescription (string, optional) - Markdown descriptioncycle (string, optional) - Cycle name, number, or IDpriority (number, optional) - 0=None, 1=Urgent, 2=High, 3=Normal, 4=Lowproject (string, optional) - Project name or IDstate (string, optional) - State type, name, or IDassignee (string, optional) - User ID, name, email, or "me"delegate (string, optional) - Agent name, displayName, or IDlabels (array of strings, optional) - Label names or IDsdueDate (string, optional) - ISO format dateparentId (string, optional) - Parent issue ID for sub-issueslinks (array of objects, optional) - Each object needs url and titleExample:
mcp__linear__create_issue({
title: "Fix authentication bug",
team: "Engineering",
description: "## Problem\n\nUsers cannot login",
state: "Todo",
labels: ["bug", "critical"],
assignee: "me"
})
update_issueUpdate an existing Linear issue.
Parameters:
id (string, required) - The issue IDtitle (string, optional)description (string, optional) - Markdownpriority (number, optional) - 0-4project (string, optional) - Project name or IDstate (string, optional) - State type, name, or IDcycle (string, optional) - Cycle name, number, or IDassignee (string, optional) - User ID, name, email, or "me"delegate (string, optional) - Agent name, displayName, or IDlabels (array of strings, optional) - Label names or IDs (replaces existing)parentId (string, optional)dueDate (string, optional) - ISO formatestimate (number, optional) - Numerical estimate valuelinks (array of objects, optional)Example:
mcp__linear__update_issue({
id: "PSN-41",
state: "In Progress",
labels: ["planning", "implementation"]
})
list_issue_statusesList available issue statuses in a Linear team.
Parameters:
team (string, required) - Team name or IDExample:
mcp__linear__list_issue_statuses({ team: "Engineering" })
get_issue_statusRetrieve detailed information about an issue status by name or ID.
Parameters:
id (string, required) - Status IDname (string, required) - Status nameteam (string, required) - Team name or IDExample:
mcp__linear__get_issue_status({ name: "In Progress", team: "Engineering" })
list_issue_labelsList available issue labels in a workspace or team.
Parameters:
limit (number, optional, max 250, default 50)before (string, optional)after (string, optional)orderBy (string, optional) - "createdAt" or "updatedAt"name (string, optional) - Filter by label nameteam (string, optional) - Team name or IDExample:
mcp__linear__list_issue_labels({ team: "Engineering" })
create_issue_labelCreate a new Linear issue label.
Parameters:
name (string, required)description (string, optional)color (string, optional) - Hex color codeteamId (string, optional) - Team UUID (workspace label if omitted)parentId (string, optional) - Parent label UUID for groupsisGroup (boolean, optional, default false) - Whether this is a label groupExample:
mcp__linear__create_issue_label({
name: "feature-request",
color: "#bb87fc",
teamId: "team-123"
})
list_project_labelsList available project labels in the Linear workspace.
Parameters:
limit (number, optional, max 250, default 50)before (string, optional)after (string, optional)orderBy (string, optional)name (string, optional)Example:
mcp__linear__list_project_labels({ limit: 100 })
list_projectsList projects in the user's Linear workspace.
Parameters:
limit (number, optional, max 250, default 50)before (string, optional)after (string, optional)orderBy (string, optional)query (string, optional) - Search project namestate (string, optional) - State name or IDinitiative (string, optional) - Initiative name or IDteam (string, optional) - Team name or IDmember (string, optional) - User ID, name, email, or "me"createdAt (string, optional)updatedAt (string, optional)includeArchived (boolean, optional, default false)Example:
mcp__linear__list_projects({ team: "Engineering", state: "started" })
get_projectRetrieve details of a specific project.
Parameters:
query (string, required) - Project ID or nameExample:
mcp__linear__get_project({ query: "CCPM" })
create_projectCreate a new project in Linear.
Parameters:
name (string, required)team (string, required) - Team name or IDsummary (string, optional) - Max 255 charsdescription (string, optional) - Markdownstate (string, optional)startDate (string, optional) - ISO formattargetDate (string, optional) - ISO formatpriority (integer, optional) - 0-4labels (array of strings, optional)lead (string, optional) - User ID, name, email, or "me"Example:
mcp__linear__create_project({
name: "Q1 Authentication",
team: "Engineering",
description: "## Goals\n\n- OAuth integration\n- SSO support",
lead: "me"
})
update_projectUpdate an existing Linear project.
Parameters:
id (string, required)name (string, optional)summary (string, optional)description (string, optional)state (string, optional)startDate (string, optional)targetDate (string, optional)priority (integer, optional) - 0-4labels (array of strings, optional)lead (string, optional)Example:
mcp__linear__update_project({
id: "proj-123",
state: "completed"
})
list_teamsList teams in the user's Linear workspace.
Parameters:
limit (number, optional, max 250, default 50)before (string, optional)after (string, optional)orderBy (string, optional)query (string, optional) - Search queryincludeArchived (boolean, optional, default false)createdAt (string, optional)updatedAt (string, optional)Example:
mcp__linear__list_teams({ includeArchived: false })
get_teamRetrieve details of a specific Linear team.
Parameters:
query (string, required) - Team UUID, key, or nameExample:
mcp__linear__get_team({ query: "Engineering" })
list_usersRetrieve users in the Linear workspace.
Parameters:
query (string, optional) - Filter by name or emailExample:
mcp__linear__list_users({ query: "john" })
get_userRetrieve details of a specific Linear user.
Parameters:
query (string, required) - User ID, name, email, or "me"Example:
mcp__linear__get_user({ query: "me" })
search_documentationSearch Linear's documentation to learn about features and usage.
Parameters:
query (string, required) - Search querypage (number, optional, default 0) - Page numberExample:
mcp__linear__search_documentation({ query: "issue statuses", page: 0 })
list_comments - List comments on issuecreate_comment - Add comment to issuelist_cycles - Get team cyclesget_document - Fetch Linear documentlist_documents - List documentsget_issue - Fetch single issuelist_issues - Search/list issuescreate_issue - Create new issueupdate_issue - Update existing issuelist_issue_statuses - List workflow statesget_issue_status - Get specific statuslist_issue_labels - List labelscreate_issue_label - Create new labellist_project_labels - List project labelslist_projects - List projectsget_project - Get specific projectcreate_project - Create new projectupdate_project - Update existing projectlist_teams - List all teamsget_team - Get specific teamlist_users - List workspace usersget_user - Get specific usersearch_documentation - Search Linear docsRULE: Every Linear operation MUST use a tool from the validated list above.
Examples of INVALID tool names that will fail:
get_issues (correct: list_issues)update_comment (correct: create new comment instead)delete_issue (not supported)list_issue_statuses (correct tool, but check args)list_* variants which are widely available// ✅ CORRECT: Use only validated tools
Task(ccpm:linear-operations): `
operation: get_issue
params:
issueId: PSN-29
context:
cache: true
`
// ❌ INCORRECT: Non-existent tool
Task(ccpm:linear-operations): `
operation: fetch_issue // This tool doesn't exist!
params:
issueId: PSN-29
`
// ❌ INCORRECT: Assuming delete exists
Task(ccpm:linear-operations): `
operation: delete_issue // Linear MCP doesn't support deletion
params:
issueId: PSN-29
`
When writing CCPM command files (files in commands/), you MUST use explicit execution instructions, NOT the YAML template format shown in the examples below.
Command files must use this format:
**Use the Task tool to fetch the issue from Linear:**
Invoke the `ccpm:linear-operations` subagent:
- **Tool**: Task
- **Subagent**: ccpm:linear-operations
- **Prompt**:
operation: get_issue params: issueId: "{the issue ID from previous step}" context: cache: true command: "work"
Why? Claude Code interprets command markdown files as executable prompts, not documentation. YAML template syntax appears as an example rather than an instruction to execute. Explicit instructions (e.g., "Use the Task tool to...") are unambiguous execution directives that ensure Claude invokes the subagent correctly.
The examples below use YAML template format for readability. Do NOT use this format in command files—use the explicit format shown above instead.
Task(ccpm:linear-operations): `
operation: <tool_name>
params:
<param1>: <value1>
<param2>: <value2>
context:
cache: true
command: "planning:plan"
`
For read operations, always enable caching to achieve 85-95% hit rates:
Task(ccpm:linear-operations): `
operation: get_issue
params:
issueId: PSN-123
context:
cache: true # Enable session-level caching
command: "planning:plan"
`
Include context to improve error messages and debugging:
Task(ccpm:linear-operations): `
operation: update_issue
params:
issueId: PSN-29
state: "In Progress"
context:
cache: false # Skip cache for writes
command: "implementation:start" # Which command triggered this
purpose: "Marking task as started" # Why we're doing this
`
The _shared-linear-helpers.md file provides convenience functions that delegate to the subagent. Use these for common operations:
Smart label management with automatic creation if missing:
// Use this instead of manual list + create
const label = await getOrCreateLabel(teamId, "feature-request");
Benefits:
Fuzzy state matching with suggestions on errors:
// Handles "In Progress" → actual state ID
const stateId = await getValidStateId(teamId, "In Progress");
Benefits:
Batch create labels if missing:
// Create multiple labels in one call
const labels = await ensureLabelsExist(teamId, [
"planning",
"implementation",
"review"
]);
Read Operations: Always enable caching
get_issue, list_issues, list_projectsWrite Operations: Disable caching
create_issue, update_issue, create_comment// READ: Cache enabled
Task(ccpm:linear-operations): `
operation: get_issue
params:
issueId: PSN-29
context:
cache: true # ✅ Cached reads
`
// WRITE: Cache disabled
Task(ccpm:linear-operations): `
operation: update_issue
params:
issueId: PSN-29
state: "Done"
context:
cache: false # ❌ Never cache writes
`
When possible, batch related operations:
// ✅ GOOD: Get all needed data in one context
Task(ccpm:linear-operations): `
operation: get_issue
params:
issueId: PSN-29
context:
cache: true
batchId: "planning-workflow"
`
// Then use Team, Project, Status in sequence
// Subsequent calls reuse cached Team/Project data
| Operation | Direct MCP | Via Subagent | Savings |
|---|---|---|---|
| Get issue | 400ms, 2.5k tokens | <50ms, 0.8k tokens | 68% |
| Update issue | 600ms, 3.2k tokens | <50ms, 1.2k tokens | 62% |
| Create comment | 500ms, 2.8k tokens | <50ms, 1.0k tokens | 64% |
| Workflow average | 500ms, 15k tokens | <50ms, 8k tokens | -47% |
The Linear subagent provides structured errors with actionable suggestions.
When updating an issue to a non-existent status:
error:
code: STATE_NOT_FOUND
message: "State 'In Review' not found in team"
params:
requestedState: "In Review"
teamId: "psn123"
suggestions:
- "Use 'In Progress' (exact match required)"
- "Available states: Backlog, Todo, In Progress, Done, Blocked"
- "Check team configuration in Linear"
Fix: Use exact state name or getValidStateId() helper
When assigning a non-existent label:
error:
code: LABEL_NOT_FOUND
message: "Label 'feature' not found"
params:
requestedLabel: "feature"
teamId: "psn123"
suggestions:
- "Label will be created automatically"
- "Use 'ensureLabelsExist()' to batch create"
- "Available labels: bug, feature-request, documentation"
Fix: Use getOrCreateLabel() or ensureLabelsExist() helpers
When accessing a non-existent issue:
error:
code: ISSUE_NOT_FOUND
message: "Issue PSN-9999 not found"
params:
issueId: "PSN-9999"
suggestions:
- "Verify issue ID is correct (use Linear UI)"
- "Check team/project context"
- "Issue may have been archived"
Fix: Validate issue ID before using
Task(ccpm:linear-operations): `
operation: get_issue
params:
issueId: PSN-29
context:
cache: true
command: "planning:plan"
purpose: "Fetch task details for planning"
`
Performance: <50ms (cached) Token cost: ~0.8k Result: Issue object with title, description, status, labels, assignee
Task(ccpm:linear-operations): `
operation: update_issue
params:
issueId: PSN-29
state: "In Progress"
labels: ["planning", "implementation"]
context:
cache: false
command: "implementation:start"
purpose: "Mark task as started with relevant labels"
`
Performance: 100-200ms (no cache) Token cost: ~1.2k Result: Updated issue with new status and labels
Task(ccpm:linear-operations): `
operation: create_comment
params:
issueId: PSN-29
body: |
Progress update:
- Implemented JWT authentication
- 2 unit tests passing
- Need to fix Redis integration
Blockers:
- Redis library compatibility
context:
cache: false
command: "implementation:sync"
purpose: "Log implementation progress"
`
Performance: 150-300ms Token cost: ~1.5k Result: Comment added to issue with timestamp
// Get or create label (delegates to subagent with caching)
const label = await getOrCreateLabel(teamId, "feature-request");
// Get valid state ID (fuzzy matching with suggestions)
const stateId = await getValidStateId(teamId, "In Progress");
// Ensure multiple labels exist
const labels = await ensureLabelsExist(teamId, [
"planning",
"implementation",
"blocked"
]);
// Now use the results
Task(ccpm:linear-operations): `
operation: update_issue
params:
issueId: PSN-29
state: ${stateId}
labels: ${labels.map(l => l.id)}
context:
cache: false
purpose: "Apply validated status and labels"
`
Performance: <100ms total (mostly cached lookups) Token cost: ~1.8k Result: Reliable label/status application with error prevention
// PATTERN: Try operation, handle structured errors
Task(ccpm:linear-operations): `
operation: update_issue
params:
issueId: PSN-29
state: "Review" // Might not exist
context:
cache: false
command: "implementation:sync"
`
// If error:
// {
// code: "STATE_NOT_FOUND",
// suggestions: ["Available states: Backlog, Todo, In Progress, Done"]
// }
// RECOVERY: Use helper or ask user
const stateId = await getValidStateId(teamId, "Review");
// Or ask user: "Which status should I use?"
Pattern: Structured errors guide user or helper selection
// Direct call - no caching, higher token cost
Task(linear-operations): `
List issues in PSN project where status = "Todo"
`
// Result: ~2.5k tokens, 400ms, no future cache hits
// Via subagent - automatic caching
Task(ccpm:linear-operations): `
operation: list_issues
params:
projectId: "PSN"
filter: {status: "Todo"}
context:
cache: true
command: "planning:plan"
`
// Result: ~0.9k tokens, <50ms, 85-95% cache hits next time
// ❌ DON'T: Forget caching
Task(ccpm:linear-operations): `
operation: get_issue
params:
issueId: PSN-29
// Missing: context: cache: true
`
// ❌ DON'T: Use non-existent tools
Task(ccpm:linear-operations): `
operation: fetch_issue_details // This doesn't exist
params:
issueId: PSN-29
`
// ❌ DON'T: Cache writes
Task(ccpm:linear-operations): `
operation: update_issue
params:
issueId: PSN-29
context:
cache: true // WRONG! Never cache writes
`
// ✅ DO: Follow patterns
Task(ccpm:linear-operations): `
operation: get_issue
params:
issueId: PSN-29
context:
cache: true // ✅ Cache reads
command: "planning:plan"
`
| Practice | Reason |
|---|---|
| Always use subagent for Linear ops | 50-60% token reduction |
| Enable caching on reads | 85-95% hit rate, <50ms performance |
| Disable caching on writes | Avoid stale data |
| Use shared helpers | Reduces duplication, better error handling |
| Validate tools against list | Prevent failures with non-existent tools |
| Provide context object | Better error messages and debugging |
| Handle structured errors | Graceful degradation and user guidance |
agents/linear-operations.mdagents/_shared-linear-helpers.mddocs/architecture/linear-subagent-architecture.mdsearch_documentation toolThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.