From 09e6caa8f432e1ca8a34bba478a4516f8c543884 Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Wed, 17 Jun 2026 17:15:53 -0700 Subject: [PATCH] feat(ci): tag triaged Linear issues with the source repository Resolve (or create) a team label named after the repository and attach it to each created CVE issue alongside the existing "CVE" label, so issues are filterable by their source repo in Linear. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/vulnerability-triage.yml | 42 +++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/.github/workflows/vulnerability-triage.yml b/.github/workflows/vulnerability-triage.yml index 2c4ccccd0..9360216b0 100644 --- a/.github/workflows/vulnerability-triage.yml +++ b/.github/workflows/vulnerability-triage.yml @@ -563,9 +563,36 @@ jobs: exit 1 fi + # Resolve (or create) a team label named after the repository, so each created + # issue is tagged with its source repo (e.g. "sourcebot-dev/sourcebot"). + REPO_LABEL_QUERY='query($teamId: String!, $name: String!) { team(id: $teamId) { labels(filter: { name: { eq: $name } }) { nodes { id } } } }' + REPO_LABEL_PAYLOAD=$(jq -n --arg query "$REPO_LABEL_QUERY" --arg teamId "$TEAM_UUID" --arg name "$REPOSITORY" \ + '{query: $query, variables: {teamId: $teamId, name: $name}}') + REPO_LABEL_RESPONSE=$(curl -s -X POST https://api.linear.app/graphql \ + -H "Content-Type: application/json" \ + -H "Authorization: $LINEAR_API_KEY" \ + -d "$REPO_LABEL_PAYLOAD") + REPO_LABEL_ID=$(echo "$REPO_LABEL_RESPONSE" | jq -r '.data.team.labels.nodes[0].id // empty') + + if [ -z "$REPO_LABEL_ID" ]; then + echo "No '$REPOSITORY' label in Linear team — creating it." + CREATE_LABEL_MUTATION='mutation($teamId: String!, $name: String!) { issueLabelCreate(input: { teamId: $teamId, name: $name }) { success issueLabel { id } } }' + CREATE_LABEL_PAYLOAD=$(jq -n --arg query "$CREATE_LABEL_MUTATION" --arg teamId "$TEAM_UUID" --arg name "$REPOSITORY" \ + '{query: $query, variables: {teamId: $teamId, name: $name}}') + CREATE_LABEL_RESPONSE=$(curl -s -X POST https://api.linear.app/graphql \ + -H "Content-Type: application/json" \ + -H "Authorization: $LINEAR_API_KEY" \ + -d "$CREATE_LABEL_PAYLOAD") + REPO_LABEL_ID=$(echo "$CREATE_LABEL_RESPONSE" | jq -r '.data.issueLabelCreate.issueLabel.id // empty') + if [ -z "$REPO_LABEL_ID" ]; then + echo "::warning::Could not create '$REPOSITORY' label: $(echo "$CREATE_LABEL_RESPONSE" | jq -c '.errors // .')" + fi + fi + { echo "team_uuid=$TEAM_UUID" echo "label_id=$LABEL_ID" + echo "repo_label_id=$REPO_LABEL_ID" echo "state_id=$STATE_ID" echo "viewer_id=$VIEWER_ID" } >> "$GITHUB_OUTPUT" @@ -644,17 +671,22 @@ jobs: REPOSITORY: ${{ github.repository }} TEAM_UUID: ${{ steps.match.outputs.team_uuid }} LABEL_ID: ${{ steps.match.outputs.label_id }} + REPO_LABEL_ID: ${{ steps.match.outputs.repo_label_id }} STATE_ID: ${{ steps.match.outputs.state_id }} VIEWER_ID: ${{ steps.match.outputs.viewer_id }} run: | set -uo pipefail - # Team UUID, "CVE" label, "Triage" state, and the API key owner's user ID were - # already resolved by the "Match existing Linear issues" step and passed in as env. + # Team UUID, the "CVE" + repository labels, "Triage" state, and the API key + # owner's user ID were already resolved by the "Match existing Linear issues" + # step and passed in as env. STRUCTURED_OUTPUT=$(cat findings.json) if [ -z "$LABEL_ID" ]; then echo "::warning::Could not find 'CVE' label in Linear team. Creating issues without label." fi + if [ -z "$REPO_LABEL_ID" ]; then + echo "::warning::Could not resolve '$REPOSITORY' repository label. Creating issues without it." + fi if [ -z "$STATE_ID" ]; then echo "::warning::Could not find 'Triage' state in Linear team. Using default state." fi @@ -756,8 +788,10 @@ jobs: --argjson priority "$PRIORITY" \ '{teamId: $teamId, title: $title, description: $desc, priority: $priority}') - if [ -n "$LABEL_ID" ]; then - VARIABLES=$(echo "$VARIABLES" | jq --arg lid "$LABEL_ID" '. + {labelIds: [$lid]}') + # Attach the "CVE" label and the repository label (dropping any that failed to resolve). + LABEL_IDS=$(jq -n --arg cve "$LABEL_ID" --arg repo "$REPO_LABEL_ID" '[$cve, $repo] | map(select(. != ""))') + if [ "$(echo "$LABEL_IDS" | jq 'length')" -gt 0 ]; then + VARIABLES=$(echo "$VARIABLES" | jq --argjson lids "$LABEL_IDS" '. + {labelIds: $lids}') fi if [ -n "$STATE_ID" ]; then VARIABLES=$(echo "$VARIABLES" | jq --arg sid "$STATE_ID" '. + {stateId: $sid}')