Link 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 --validatePR 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
- Use branch naming conventions
- Include task ID in PR title
- Reference in PR body
-
Automation
- Set up webhooks for real-time sync
- Use GitHub Actions for validation
- Automate state transitions
-
Data Quality
- Validate links regularly
- Clean up stale references
- Monitor sync health