Link pull requests to Linear tasks
Links GitHub pull requests to Linear tasks and synchronizes their states bidirectionally.
/plugin marketplace add davepoon/buildwithclaude/plugin install commands-integration-sync@buildwithclaudeLink pull requests to Linear tasks
You are a PR-to-task synchronization specialist that connects GitHub pull requests with Linear tasks. You extract task references, update statuses bidirectionally, and maintain development workflow integration.
When syncing pull requests to Linear tasks:
Detect Linear References
function extractLinearRefs(pr) {
const patterns = [
/([A-Z]{2,5}-\d+)/g, // ABC-123
/linear\.app\/.*\/issue\/([A-Z]{2,5}-\d+)/g, // Linear URLs
/(?:fixes|closes|resolves)\s+([A-Z]{2,5}-\d+)/gi // Keywords
];
const refs = new Set();
const searchText = `${pr.title} ${pr.body}`;
for (const pattern of patterns) {
const matches = searchText.matchAll(pattern);
for (const match of matches) {
refs.add(match[1].toUpperCase());
}
}
return Array.from(refs);
}
Fetch PR Details
# Get PR information
gh pr view <pr-number> --json \
number,title,body,state,draft,author,assignees,\
labels,milestone,createdAt,updatedAt,mergedAt,\
commits,additions,deletions,changedFiles,reviews
PR State Mapping
function mapPRStateToLinear(pr) {
if (pr.draft) return 'Backlog';
if (pr.state === 'CLOSED' && !pr.merged) return 'Canceled';
if (pr.merged) return 'Done';
// Check reviews
const hasApprovals = pr.reviews.some(r => r.state === 'APPROVED');
const hasRequestedChanges = pr.reviews.some(r => r.state === 'CHANGES_REQUESTED');
if (hasRequestedChanges) return 'Todo';
if (hasApprovals) return 'In Review';
if (pr.state === 'OPEN') return 'In Progress';
return 'Todo';
}
Update Linear Task
async function updateLinearTask(taskId, prData) {
const updates = {
// Update state based on PR
state: mapPRStateToLinear(prData),
// Add PR link to description
description: appendPRLink(task.description, prData.url),
// Update custom fields
customFields: {
githubPR: prData.number,
prStatus: prData.state,
prAuthor: prData.author.login
}
};
// Add PR labels
if (prData.labels.includes('bug')) {
updates.labels = [...task.labels, 'Has PR', 'Bug Fix'];
}
await linear.updateIssue(taskId, updates);
}
Create Linear Comment
function createPRComment(taskId, pr) {
const comment = `
š **Pull Request ${pr.draft ? 'Draft ' : ''}#${pr.number}**
**Title:** ${pr.title}
**Author:** @${pr.author.login}
**Status:** ${pr.state} ${pr.merged ? '(Merged)' : ''}
**Changes:** +${pr.additions} -${pr.deletions} in ${pr.changedFiles} files
**Reviews:**
${formatReviews(pr.reviews)}
[View on GitHub](${pr.url})
`;
return linear.createComment(taskId, { body: comment });
}
Update PR with Linear Info
# Add Linear task info to PR
gh pr comment <pr-number> --body "
## Linear Task: $TASK_ID
This PR addresses: [$TASK_ID - $TASK_TITLE]($TASK_URL)
**Task Status:** $TASK_STATUS
**Priority:** $TASK_PRIORITY
**Assignee:** $TASK_ASSIGNEE
"
# Add labels
gh pr edit <pr-number> --add-label "linear:$TASK_ID"
Automated Status Updates
// PR event handlers
const prEventHandlers = {
'opened': async (pr, taskId) => {
await updateTaskState(taskId, 'In Progress');
await addComment(taskId, 'PR opened');
},
'ready_for_review': async (pr, taskId) => {
await updateTaskState(taskId, 'In Review');
await addComment(taskId, 'PR ready for review');
},
'merged': async (pr, taskId) => {
await updateTaskState(taskId, 'Done');
await addComment(taskId, 'PR merged');
},
'closed': async (pr, taskId) => {
if (!pr.merged) {
await addComment(taskId, 'PR closed without merging');
}
}
};
Branch Detection
function detectTaskFromBranch(branchName) {
// Common patterns
const patterns = [
/^(?:feature|fix|bug)\/([A-Z]{2,5}-\d+)/, // feature/ABC-123
/^([A-Z]{2,5}-\d+)/, // ABC-123
/([A-Z]{2,5}-\d+)$/ // anything-ABC-123
];
for (const pattern of patterns) {
const match = branchName.match(pattern);
if (match) return match[1];
}
return null;
}
Webhook Configuration
# GitHub webhook events
events:
- pull_request.opened
- pull_request.closed
- pull_request.ready_for_review
- pull_request.converted_to_draft
- pull_request_review.submitted
- pull_request.merged
Sync Validation
async function validateSync(pr, task) {
const warnings = [];
// Check assignee match
if (pr.assignees[0]?.login !== mapToGitHub(task.assignee)) {
warnings.push('Assignee mismatch between PR and task');
}
// Check labels
if (!hasMatchingLabels(pr.labels, task.labels)) {
warnings.push('Label inconsistency detected');
}
// Check milestone/project
if (pr.milestone?.title !== task.project?.name) {
warnings.push('Different milestone/project');
}
return warnings;
}
# Link PR to Linear task
claude sync-pr-to-task 123 --task="ABC-456"
# Auto-detect task from PR
claude sync-pr-to-task 123
# Link multiple PRs
claude sync-pr-to-task 123,124,125 --task="ABC-456"
# Enable auto-sync for repository
claude sync-pr-to-task --enable-auto --repo="owner/repo"
# Configure sync behavior
claude sync-pr-to-task --config \
--update-state="true" \
--sync-reviews="true" \
--sync-labels="true"
# Check PR-task links
claude sync-pr-to-task --status
# Find unlinked PRs
claude sync-pr-to-task --find-unlinked
# Validate existing links
claude sync-pr-to-task --validate
PR to Linear Task Sync
======================
Repository: owner/repo
PR: #123 - Implement caching layer
Linear Task Detection:
ā Found task reference: ABC-456
ā Task exists in Linear
ā Task is in "In Progress" state
Sync Actions:
ā Updated Linear task state ā "In Review"
ā Added PR link to task description
ā Created comment in Linear with PR details
ā Added "linear:ABC-456" label to PR
ā Posted Linear task summary to PR
Validation Results:
ā Assignees match
ā Label mismatch: PR has "enhancement", task has "feature"
ā Both targeting same milestone
Automated Sync: Enabled
Next sync: On PR update
const stateSync = {
// PR state ā Linear state
prToLinear: {
'draft': 'Backlog',
'open': 'In Progress',
'ready_for_review': 'In Review',
'merged': 'Done',
'closed': null // Don't change
},
// Linear state ā PR action
linearToPR: {
'Backlog': 'convert_to_draft',
'In Progress': 'ready_for_review',
'Done': 'merge',
'Canceled': 'close'
}
};
async function analyzeCommits(pr, taskId) {
const commits = await getPRCommits(pr.number);
const analysis = {
totalCommits: commits.length,
authors: new Set(commits.map(c => c.author)),
timeSpent: calculateTimeSpent(commits),
filesChanged: await getChangedFiles(pr.number),
testCoverage: await getTestCoverage(pr.number)
};
// Update Linear task with insights
await updateTaskWithMetrics(taskId, analysis);
}
Clear References
Automation
Data Quality