Skip to content

Conversation

@nearestnabors
Copy link
Contributor

@nearestnabors nearestnabors commented Dec 10, 2025

Note

Adds a GitHub Actions workflow to auto-create a Linear issue when a PR is assigned or review is requested and comments the issue link on the PR.

  • CI / GitHub Actions (.github/workflows/linear.yml):
    • Triggers on pull_request events: assigned, review_requested, review_request_removed (job runs only for the first two).
    • Extracts PR metadata (title, number, URL, repo) and creates a Linear issue via GraphQL using secrets for API/team/project.
    • Maps GitHub assignee/reviewer usernames to Linear assignee IDs before creation (optional assignment).
    • Comments back on the PR with the created Linear issue URL.

Written by Cursor Bugbot for commit cef91e6. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Dec 10, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
docs Ready Ready Preview Comment Dec 10, 2025 2:17am

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

- name: Extract PR info
id: pr
run: |
echo "title=${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Script injection vulnerability via PR title interpolation

The PR title is directly interpolated into the shell command using ${{ github.event.pull_request.title }}. An attacker can craft a malicious PR title containing shell metacharacters (like "; malicious_command #) to execute arbitrary commands in the workflow runner. This is a known GitHub Actions script injection vulnerability. The title value is later used again on lines 45-46, propagating the risk. The safe approach is to pass untrusted input through environment variables rather than direct interpolation.

Additional Locations (1)

Fix in Cursor Fix in Web

env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ISSUE_URL=$(echo '${{ steps.linear.outputs.response }}' | jq -r '.data.issueCreate.issue.url // empty')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Script injection via API response interpolation

The Linear API response stored in steps.linear.outputs.response is directly interpolated into the shell using ${{ steps.linear.outputs.response }}. If the response contains shell metacharacters or if an attacker can influence the API response, this could lead to command injection. External API responses are untrusted input and need to be passed through environment variables rather than direct interpolation.

Fix in Cursor Fix in Web

--arg projId "$LINEAR_PROJECT_ID" \
--arg assigneeId "$LINEAR_ASSIGNEE_ID" \
'{
query: $ENV.QUERY,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Shell variable not exported for jq $ENV access

The QUERY variable is defined as a shell variable (lines 48-56) but is accessed via $ENV.QUERY in the jq command. The $ENV object in jq only contains exported environment variables, not local shell variables. Since QUERY is never exported, $ENV.QUERY will be null, resulting in a malformed GraphQL request with no query field. The mutation will fail silently.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a GitHub Actions workflow that automatically creates Linear tickets when PRs are assigned or when reviewers are requested. The workflow extracts PR information, maps GitHub usernames to Linear user IDs, creates a Linear issue via GraphQL API, and comments on the PR with the created issue link.

Key Changes:

  • Adds automation to create Linear tracking tickets for PR reviews
  • Maps GitHub usernames to Linear user IDs for automatic assignment
  • Integrates Linear workflow with GitHub PR lifecycle events

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +44
run: |
# Pick whoever got assigned / requested
ASSIGNEE="${GITHUB_ASSIGNEE:-$GITHUB_REVIEWER}"

# Simple mapping GitHub username -> Linear user ID (adjust to your team)
case "$ASSIGNEE" in
"nearestnabors") LINEAR_ASSIGNEE_ID="nearestnabors" ;;
"torresmateo") LINEAR_ASSIGNEE_ID="mateo" ;;
"vfanelle") LINEAR_ASSIGNEE_ID="valerie" ;;
"avoguru") LINEAR_ASSIGNEE_ID="guru" ;;
*) LINEAR_ASSIGNEE_ID="" ;;
esac

Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a team is requested for review instead of an individual, github.event.requested_reviewer will be null, and github.event.requested_team should be used instead. This workflow doesn't handle team review requests, which will cause the Linear assignee to be empty in those cases. Consider adding logic to handle team review requests or documenting that only individual reviewer assignments are supported.

Suggested change
run: |
# Pick whoever got assigned / requested
ASSIGNEE="${GITHUB_ASSIGNEE:-$GITHUB_REVIEWER}"
# Simple mapping GitHub username -> Linear user ID (adjust to your team)
case "$ASSIGNEE" in
"nearestnabors") LINEAR_ASSIGNEE_ID="nearestnabors" ;;
"torresmateo") LINEAR_ASSIGNEE_ID="mateo" ;;
"vfanelle") LINEAR_ASSIGNEE_ID="valerie" ;;
"avoguru") LINEAR_ASSIGNEE_ID="guru" ;;
*) LINEAR_ASSIGNEE_ID="" ;;
esac
GITHUB_REVIEW_TEAM: ${{ github.event.requested_team.name }}
run: |
# Pick whoever got assigned / requested
ASSIGNEE="${GITHUB_ASSIGNEE:-$GITHUB_REVIEWER}"
# If no individual assignee, check for team review request
if [ -z "$ASSIGNEE" ] && [ -n "$GITHUB_REVIEW_TEAM" ]; then
# Map team name to Linear user ID (adjust to your team mapping)
case "$GITHUB_REVIEW_TEAM" in
"frontend-team") LINEAR_ASSIGNEE_ID="valerie" ;; # example mapping
"backend-team") LINEAR_ASSIGNEE_ID="mateo" ;; # example mapping
*) LINEAR_ASSIGNEE_ID="" ;; # no mapping, leave unassigned
esac
else
# Simple mapping GitHub username -> Linear user ID (adjust to your team)
case "$ASSIGNEE" in
"nearestnabors") LINEAR_ASSIGNEE_ID="nearestnabors" ;;
"torresmateo") LINEAR_ASSIGNEE_ID="mateo" ;;
"vfanelle") LINEAR_ASSIGNEE_ID="valerie" ;;
"avoguru") LINEAR_ASSIGNEE_ID="guru" ;;
*) LINEAR_ASSIGNEE_ID="" ;;
esac
fi

Copilot uses AI. Check for mistakes.
echo "response=$RESPONSE" >> $GITHUB_OUTPUT

- name: Comment on PR with Linear issue
if: always()
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if: always() condition means this step will run even if the Linear issue creation fails or the workflow is cancelled. This could lead to misleading comments on the PR. Consider using if: success() or checking steps.linear.outcome == 'success' to only comment when the Linear issue was actually created successfully.

Suggested change
if: always()
if: steps.linear.outcome == 'success'

Copilot uses AI. Check for mistakes.
LINEAR_PROJECT_ID: ${{ secrets.LINEAR_PROJECT_ID }}
GITHUB_ASSIGNEE: ${{ github.event.assignee.login }}
GITHUB_REVIEWER: ${{ github.event.requested_reviewer.login }}
run: |
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow doesn't validate that required secrets (LINEAR_API_KEY, LINEAR_TEAM_ID) are set before making the API call. If these secrets are missing, the curl request will fail silently, and the response parsing will fail with a cryptic error. Consider adding validation at the start of the script to check for required environment variables and exit with a clear error message if they're missing.

Suggested change
run: |
run: |
# Validate required secrets
if [ -z "$LINEAR_API_KEY" ]; then
echo "Error: Required secret LINEAR_API_KEY is not set." >&2
exit 1
fi
if [ -z "$LINEAR_TEAM_ID" ]; then
echo "Error: Required secret LINEAR_TEAM_ID is not set." >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
-H "Authorization: Bearer $LINEAR_API_KEY" \
-d "$INPUT" \
https://api.linear.app/graphql)

Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow doesn't check if the Linear API request was successful before saving the response. GraphQL APIs can return errors in the response body with a 200 status code. The response should be validated to check for errors (e.g., .errors field) before proceeding. Without this check, the workflow might appear to succeed even when the Linear issue creation failed.

Suggested change
# Check for errors in the Linear API response
ERRORS=$(echo "$RESPONSE" | jq '.errors')
if [ "$ERRORS" != "null" ]; then
echo "Linear API returned errors: $ERRORS"
exit 1
fi

Copilot uses AI. Check for mistakes.
- name: Extract PR info
id: pr
run: |
echo "title=${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title is being passed directly to GITHUB_OUTPUT without proper escaping. If the PR title contains special characters like newlines, %, or \r, it could break the output or cause unexpected behavior. Consider using proper escaping or the multiline string syntax for GitHub Actions outputs:

{
  echo "title<<EOF"
  echo "${{ github.event.pull_request.title }}"
  echo "EOF"
} >> $GITHUB_OUTPUT
Suggested change
echo "title=${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT
{
echo "title<<EOF" >> $GITHUB_OUTPUT
echo "${{ github.event.pull_request.title }}" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
}

Copilot uses AI. Check for mistakes.
esac

TITLE="Review PR: ${{ steps.pr.outputs.title }}"
DESCRIPTION="GitHub PR: ${{ steps.pr.outputs.url }}\nRepository: ${{ steps.pr.outputs.repo }}\nPR #${{ steps.pr.outputs.number }}"
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The \n in the DESCRIPTION variable will be treated as literal characters by the shell, not as newlines. To properly include newlines in the description, use $'\n' or actual newline characters in the string. For example: DESCRIPTION="GitHub PR: ${{ steps.pr.outputs.url }}$'\n'Repository: ..."

Suggested change
DESCRIPTION="GitHub PR: ${{ steps.pr.outputs.url }}\nRepository: ${{ steps.pr.outputs.repo }}\nPR #${{ steps.pr.outputs.number }}"
DESCRIPTION=$'GitHub PR: ${{ steps.pr.outputs.url }}\nRepository: ${{ steps.pr.outputs.repo }}\nPR #${{ steps.pr.outputs.number }}'

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +76
INPUT=$(jq -n \
--arg title "$TITLE" \
--arg desc "$DESCRIPTION" \
--arg teamId "$LINEAR_TEAM_ID" \
--arg projId "$LINEAR_PROJECT_ID" \
--arg assigneeId "$LINEAR_ASSIGNEE_ID" \
'{
query: $ENV.QUERY,
variables: {
input: {
title: $title,
description: $desc,
teamId: $teamId,
projectId: ($projId | select(. != "")),
assigneeId: ($assigneeId | select(. != ""))
}
}
}'
)
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSON structure being built is incorrect for a GraphQL request. The query field should be at the root level alongside variables, but this jq command is placing query: $ENV.QUERY inside the JSON (which won't work as $ENV.QUERY is not a valid jq reference). The correct structure should be:

{
  "query": "mutation IssueCreate(...) { ... }",
  "variables": { "input": { ... } }
}

The jq command should be restructured to properly include the QUERY variable at the root level.

Copilot uses AI. Check for mistakes.
run: |
ISSUE_URL=$(echo '${{ steps.linear.outputs.response }}' | jq -r '.data.issueCreate.issue.url // empty')
if [ -n "$ISSUE_URL" ]; then
gh pr comment ${{ steps.pr.outputs.number }} --body "Created Linear issue: $ISSUE_URL"
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gh pr comment command is missing the --repo flag. When run in a workflow context without a checked-out repository, the command needs to specify which repository the PR belongs to. Add --repo ${{ github.repository }} to the command.

Suggested change
gh pr comment ${{ steps.pr.outputs.number }} --body "Created Linear issue: $ISSUE_URL"
gh pr comment ${{ steps.pr.outputs.number }} --repo ${{ github.repository }} --body "Created Linear issue: $ISSUE_URL"

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +41
"nearestnabors") LINEAR_ASSIGNEE_ID="nearestnabors" ;;
"torresmateo") LINEAR_ASSIGNEE_ID="mateo" ;;
"vfanelle") LINEAR_ASSIGNEE_ID="valerie" ;;
"avoguru") LINEAR_ASSIGNEE_ID="guru" ;;
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Linear assignee IDs in the case statement appear to be usernames rather than actual Linear user IDs. Linear's API requires UUIDs for the assigneeId field (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). These string values like "nearestnabors", "mateo", "valerie", and "guru" are likely not valid Linear user IDs and will cause the assignment to fail silently. You need to use the actual Linear user UUIDs, which can be obtained from the Linear API or Linear settings.

Suggested change
"nearestnabors") LINEAR_ASSIGNEE_ID="nearestnabors" ;;
"torresmateo") LINEAR_ASSIGNEE_ID="mateo" ;;
"vfanelle") LINEAR_ASSIGNEE_ID="valerie" ;;
"avoguru") LINEAR_ASSIGNEE_ID="guru" ;;
# Replace the UUIDs below with the actual Linear user IDs for each assignee
"nearestnabors") LINEAR_ASSIGNEE_ID="11111111-1111-1111-1111-111111111111" ;; # TODO: Replace with actual UUID
"torresmateo") LINEAR_ASSIGNEE_ID="22222222-2222-2222-2222-222222222222" ;; # TODO: Replace with actual UUID
"vfanelle") LINEAR_ASSIGNEE_ID="33333333-3333-3333-3333-333333333333" ;; # TODO: Replace with actual UUID
"avoguru") LINEAR_ASSIGNEE_ID="44444444-4444-4444-4444-444444444444" ;; # TODO: Replace with actual UUID

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants