4343 required : false
4444 type : boolean
4545 default : false
46+ linear_assignee_id :
47+ description : ' Linear user UUID to assign. Leave empty to use assign_to_api_key_owner behavior.'
48+ required : false
49+ type : string
50+ default : ' '
51+ assign_to_api_key_owner :
52+ description : ' Assign issues to the Linear API key owner when linear_assignee_id is empty.'
53+ required : false
54+ type : boolean
55+ default : true
56+ linear_state_name :
57+ description : ' Linear workflow state for newly created or reopened issues.'
58+ required : false
59+ type : string
60+ default : ' Triage'
61+ linear_cycle_id :
62+ description : ' Linear cycle UUID to assign. Takes precedence over linear_use_active_cycle.'
63+ required : false
64+ type : string
65+ default : ' '
66+ linear_use_active_cycle :
67+ description : ' Assign issues to the Linear team active cycle when linear_cycle_id is empty.'
68+ required : false
69+ type : boolean
70+ default : false
4671 secrets :
4772 LINEAR_API_KEY :
4873 required : true
@@ -58,6 +83,7 @@ permissions:
5883 contents : read
5984 packages : read
6085 security-events : read # Required for CodeQL alerts API
86+ vulnerability-alerts : read # Required for Dependabot alerts API
6187 id-token : write # Required for OIDC authentication
6288
6389jobs :
@@ -133,7 +159,7 @@ jobs:
133159 id : check
134160 env :
135161 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
136- DEPENDABOT_PAT : ${{ secrets.DEPENDABOT_PAT }}
162+ DEPENDABOT_PAT : ${{ secrets.DEPENDABOT_PAT || secrets.GITHUB_TOKEN }}
137163 run : |
138164 HAS_ALERTS=false
139165
@@ -184,7 +210,7 @@ jobs:
184210 - name : Write alerts summary
185211 env :
186212 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
187- DEPENDABOT_PAT : ${{ secrets.DEPENDABOT_PAT }}
213+ DEPENDABOT_PAT : ${{ secrets.DEPENDABOT_PAT || secrets.GITHUB_TOKEN }}
188214 run : |
189215 echo "## Dependabot & CodeQL Alert Check" >> "$GITHUB_STEP_SUMMARY"
190216 echo "" >> "$GITHUB_STEP_SUMMARY"
@@ -252,8 +278,6 @@ jobs:
252278 steps :
253279 - name : Checkout repository
254280 uses : actions/checkout@v4
255- with :
256- submodules : recursive
257281
258282 - name : Download scan results
259283 if : needs.scan.outputs.has_vulnerabilities == 'true'
@@ -279,7 +303,7 @@ jobs:
279303
280304 - name : Fetch Dependabot alerts
281305 env :
282- DEPENDABOT_PAT : ${{ secrets.DEPENDABOT_PAT }}
306+ DEPENDABOT_PAT : ${{ secrets.DEPENDABOT_PAT || secrets.GITHUB_TOKEN }}
283307 run : |
284308 if [ -z "$DEPENDABOT_PAT" ]; then
285309 echo "::warning::DEPENDABOT_PAT not configured. Writing empty Dependabot alerts."
@@ -541,13 +565,17 @@ jobs:
541565 LINEAR_API_KEY : ${{ secrets.LINEAR_API_KEY }}
542566 LINEAR_TEAM_ID : ${{ secrets.LINEAR_TEAM_ID }}
543567 REPOSITORY : ${{ github.repository }}
568+ LINEAR_STATE_NAME : ${{ inputs.linear_state_name || 'Triage' }}
544569 run : |
545570 set -euo pipefail
546- # Resolve team UUID + the "CVE" label, "Triage" state, and API key owner once,
547- # and expose them as outputs so the issue-creation step can reuse them.
548- METADATA_QUERY='query($teamId: String!) { team(id: $teamId) { id labels(filter: { name: { eq: "CVE" } }) { nodes { id } } states(filter: { name: { eq: "Triage" } }) { nodes { id } } } viewer { id } }'
549- METADATA_PAYLOAD=$(jq -n --arg query "$METADATA_QUERY" --arg teamId "$LINEAR_TEAM_ID" \
550- '{query: $query, variables: {teamId: $teamId}}')
571+ # Resolve team UUID, labels, configured state, active cycle, and API key
572+ # owner once, then expose them for issue creation and reopening.
573+ METADATA_QUERY='query($teamId: String!, $stateName: String!) { team(id: $teamId) { id labels(filter: { name: { eq: "CVE" } }) { nodes { id } } states(filter: { name: { eq: $stateName } }) { nodes { id } } activeCycle { id } } viewer { id } }'
574+ METADATA_PAYLOAD=$(jq -n \
575+ --arg query "$METADATA_QUERY" \
576+ --arg teamId "$LINEAR_TEAM_ID" \
577+ --arg stateName "$LINEAR_STATE_NAME" \
578+ '{query: $query, variables: {teamId: $teamId, stateName: $stateName}}')
551579 METADATA_RESPONSE=$(curl -s -X POST https://api.linear.app/graphql \
552580 -H "Content-Type: application/json" \
553581 -H "Authorization: $LINEAR_API_KEY" \
@@ -556,6 +584,7 @@ jobs:
556584 TEAM_UUID=$(echo "$METADATA_RESPONSE" | jq -r '.data.team.id // empty')
557585 LABEL_ID=$(echo "$METADATA_RESPONSE" | jq -r '.data.team.labels.nodes[0].id // empty')
558586 STATE_ID=$(echo "$METADATA_RESPONSE" | jq -r '.data.team.states.nodes[0].id // empty')
587+ ACTIVE_CYCLE_ID=$(echo "$METADATA_RESPONSE" | jq -r '.data.team.activeCycle.id // empty')
559588 VIEWER_ID=$(echo "$METADATA_RESPONSE" | jq -r '.data.viewer.id // empty')
560589
561590 if [ -z "$TEAM_UUID" ]; then
@@ -594,6 +623,7 @@ jobs:
594623 echo "label_id=$LABEL_ID"
595624 echo "repo_label_id=$REPO_LABEL_ID"
596625 echo "state_id=$STATE_ID"
626+ echo "active_cycle_id=$ACTIVE_CYCLE_ID"
597627 echo "viewer_id=$VIEWER_ID"
598628 } >> "$GITHUB_OUTPUT"
599629
@@ -673,25 +703,42 @@ jobs:
673703 LABEL_ID : ${{ steps.match.outputs.label_id }}
674704 REPO_LABEL_ID : ${{ steps.match.outputs.repo_label_id }}
675705 STATE_ID : ${{ steps.match.outputs.state_id }}
706+ ACTIVE_CYCLE_ID : ${{ steps.match.outputs.active_cycle_id }}
676707 VIEWER_ID : ${{ steps.match.outputs.viewer_id }}
708+ CONFIGURED_ASSIGNEE_ID : ${{ inputs.linear_assignee_id }}
709+ ASSIGN_TO_API_KEY_OWNER : ${{ inputs.assign_to_api_key_owner }}
710+ LINEAR_STATE_NAME : ${{ inputs.linear_state_name || 'Triage' }}
711+ CONFIGURED_CYCLE_ID : ${{ inputs.linear_cycle_id }}
712+ USE_ACTIVE_CYCLE : ${{ inputs.linear_use_active_cycle }}
677713 run : |
678714 set -uo pipefail
679- # Team UUID, the "CVE" + repository labels, "Triage" state, and the API key
680- # owner's user ID were already resolved by the "Match existing Linear issues"
681- # step and passed in as env.
715+ # Team UUID, labels, state, cycle, and assignee metadata were already
716+ # resolved by the "Match existing Linear issues" step.
682717 STRUCTURED_OUTPUT=$(cat findings.json)
683718
719+ TARGET_ASSIGNEE_ID="$CONFIGURED_ASSIGNEE_ID"
720+ if [ -z "$TARGET_ASSIGNEE_ID" ] && [ "$ASSIGN_TO_API_KEY_OWNER" = "true" ]; then
721+ TARGET_ASSIGNEE_ID="$VIEWER_ID"
722+ fi
723+ TARGET_CYCLE_ID="$CONFIGURED_CYCLE_ID"
724+ if [ -z "$TARGET_CYCLE_ID" ] && [ "$USE_ACTIVE_CYCLE" = "true" ]; then
725+ TARGET_CYCLE_ID="$ACTIVE_CYCLE_ID"
726+ fi
727+
684728 if [ -z "$LABEL_ID" ]; then
685729 echo "::warning::Could not find 'CVE' label in Linear team. Creating issues without label."
686730 fi
687731 if [ -z "$REPO_LABEL_ID" ]; then
688732 echo "::warning::Could not resolve '$REPOSITORY' repository label. Creating issues without it."
689733 fi
690734 if [ -z "$STATE_ID" ]; then
691- echo "::warning::Could not find 'Triage' state in Linear team. Using default state."
735+ echo "::warning::Could not find '$LINEAR_STATE_NAME' state in Linear team. Using default state."
736+ fi
737+ if [ -z "$TARGET_ASSIGNEE_ID" ]; then
738+ echo "Issues will be created or reopened unassigned."
692739 fi
693- if [ -z "$VIEWER_ID " ]; then
694- echo "::warning::Could not resolve Linear API key owner . Issues will be created unassigned ."
740+ if [ "$USE_ACTIVE_CYCLE" = "true" ] && [ -z "$TARGET_CYCLE_ID " ]; then
741+ echo "::warning::Could not resolve an active Linear cycle . Issues will be created without a cycle ."
695742 fi
696743
697744 # Map severity to Linear priority
@@ -716,7 +763,7 @@ jobs:
716763 # Write CVEs to temp file so the while loop doesn't run in a pipe subshell
717764 echo "$STRUCTURED_OUTPUT" | jq -c '.cves[]' > /tmp/cves.jsonl
718765
719- MUTATION='mutation CreateIssue($teamId: String!, $title: String!, $description: String, $priority: Int, $labelIds: [String!], $stateId: String, $assigneeId: String) { issueCreate(input: { teamId: $teamId, title: $title, description: $description, priority: $priority, labelIds: $labelIds, stateId: $stateId, assigneeId: $assigneeId }) { success issue { id identifier url } } }'
766+ MUTATION='mutation CreateIssue($teamId: String!, $title: String!, $description: String, $priority: Int, $labelIds: [String!], $stateId: String, $assigneeId: String, $cycleId: String ) { issueCreate(input: { teamId: $teamId, title: $title, description: $description, priority: $priority, labelIds: $labelIds, stateId: $stateId, assigneeId: $assigneeId, cycleId: $cycleId }) { success issue { id identifier url } } }'
720767
721768 while IFS= read -r cve; do
722769 CVE_ID=$(echo "$cve" | jq -r '.cveId')
@@ -737,22 +784,27 @@ jobs:
737784 fi
738785
739786 if [ "$LINEAR_EXISTS" = "true" ] && [ "$LINEAR_CLOSED" = "true" ]; then
740- # Reopen the closed issue by setting its state back to Triage
787+ # Reopen the closed issue using the configured repository routing.
741788 echo "Found closed Linear issue $LINEAR_IDENTIFIER for $CVE_ID ($LINEAR_URL) — will attempt to reopen"
742789 if [ -z "$STATE_ID" ]; then
743- echo "::warning::Cannot reopen $CVE_ID ($LINEAR_IDENTIFIER) — no Triage state found. Skipping."
744- echo "- Skipped **$CVE_ID** — found closed issue [$LINEAR_IDENTIFIER]($LINEAR_URL) but no Triage state to reopen" >> "$GITHUB_STEP_SUMMARY"
790+ echo "::warning::Cannot reopen $CVE_ID ($LINEAR_IDENTIFIER) — no '$LINEAR_STATE_NAME' state found. Skipping."
791+ echo "- Skipped **$CVE_ID** — found closed issue [$LINEAR_IDENTIFIER]($LINEAR_URL) but no '$LINEAR_STATE_NAME' state to reopen" >> "$GITHUB_STEP_SUMMARY"
745792 SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
746793 continue
747794 fi
748795
749- REOPEN_MUTATION='mutation($issueId: String!, $stateId: String!, $assigneeId: String) { issueUpdate(id: $issueId, input: { stateId: $stateId, assigneeId: $assigneeId }) { success issue { id identifier url } } }'
796+ REOPEN_MUTATION='mutation($issueId: String!, $stateId: String!, $assigneeId: String, $cycleId: String ) { issueUpdate(id: $issueId, input: { stateId: $stateId, assigneeId: $assigneeId, cycleId: $cycleId }) { success issue { id identifier url } } }'
750797 REOPEN_VARIABLES=$(jq -n \
751798 --arg issueId "$LINEAR_ISSUE_ID" \
752799 --arg stateId "$STATE_ID" \
753800 '{issueId: $issueId, stateId: $stateId}')
754- if [ -n "$VIEWER_ID" ]; then
755- REOPEN_VARIABLES=$(echo "$REOPEN_VARIABLES" | jq --arg aid "$VIEWER_ID" '. + {assigneeId: $aid}')
801+ if [ -n "$TARGET_ASSIGNEE_ID" ]; then
802+ REOPEN_VARIABLES=$(echo "$REOPEN_VARIABLES" | jq --arg aid "$TARGET_ASSIGNEE_ID" '. + {assigneeId: $aid}')
803+ else
804+ REOPEN_VARIABLES=$(echo "$REOPEN_VARIABLES" | jq '. + {assigneeId: null}')
805+ fi
806+ if [ -n "$TARGET_CYCLE_ID" ]; then
807+ REOPEN_VARIABLES=$(echo "$REOPEN_VARIABLES" | jq --arg cid "$TARGET_CYCLE_ID" '. + {cycleId: $cid}')
756808 fi
757809 REOPEN_PAYLOAD=$(jq -n --arg query "$REOPEN_MUTATION" --argjson vars "$REOPEN_VARIABLES" '{query: $query, variables: $vars}')
758810
@@ -765,7 +817,7 @@ jobs:
765817 REOPEN_IDENTIFIER=$(echo "$REOPEN_RESPONSE" | jq -r '.data.issueUpdate.issue.identifier // empty')
766818 if [ -n "$REOPEN_URL" ]; then
767819 echo "Reopened Linear issue $REOPEN_IDENTIFIER for $CVE_ID: $REOPEN_URL"
768- echo "- Reopened [$REOPEN_IDENTIFIER]($REOPEN_URL) for **$CVE_ID** — $TITLE (moved back to Triage )" >> "$GITHUB_STEP_SUMMARY"
820+ echo "- Reopened [$REOPEN_IDENTIFIER]($REOPEN_URL) for **$CVE_ID** — $TITLE (moved to $LINEAR_STATE_NAME )" >> "$GITHUB_STEP_SUMMARY"
769821 REOPENED_COUNT=$((REOPENED_COUNT + 1))
770822 else
771823 echo "::error::Failed to reopen Linear issue $LINEAR_IDENTIFIER for $CVE_ID"
@@ -796,8 +848,13 @@ jobs:
796848 if [ -n "$STATE_ID" ]; then
797849 VARIABLES=$(echo "$VARIABLES" | jq --arg sid "$STATE_ID" '. + {stateId: $sid}')
798850 fi
799- if [ -n "$VIEWER_ID" ]; then
800- VARIABLES=$(echo "$VARIABLES" | jq --arg aid "$VIEWER_ID" '. + {assigneeId: $aid}')
851+ if [ -n "$TARGET_ASSIGNEE_ID" ]; then
852+ VARIABLES=$(echo "$VARIABLES" | jq --arg aid "$TARGET_ASSIGNEE_ID" '. + {assigneeId: $aid}')
853+ else
854+ VARIABLES=$(echo "$VARIABLES" | jq '. + {assigneeId: null}')
855+ fi
856+ if [ -n "$TARGET_CYCLE_ID" ]; then
857+ VARIABLES=$(echo "$VARIABLES" | jq --arg cid "$TARGET_CYCLE_ID" '. + {cycleId: $cid}')
801858 fi
802859
803860 PAYLOAD=$(jq -n --arg query "$MUTATION" --argjson vars "$VARIABLES" '{query: $query, variables: $vars}')
@@ -827,4 +884,4 @@ jobs:
827884 if [ "$FAILED_COUNT" -gt 0 ]; then
828885 echo "::error::Failed to create $FAILED_COUNT Linear issue(s)"
829886 exit 1
830- fi
887+ fi
0 commit comments