Skip to content

Commit 8300542

Browse files
committed
Add PR linked-issue check action and workflow
Introduce a composite GitHub Action that verifies a pull request has at least one linked issue via the GraphQL closingIssuesReferences field, plus a reusable workflow to run it. Adds action implementation (.github/actions/check-linked-issue/action.yml) and documentation (README.md) and a reusable workflow (.github/workflows/check-issue.yml). The action uses actions/github-script to query linked issues and fails the step if none are found; the workflow demonstrates usage and required permissions (contents: read, pull-requests: read). Also removes a placeholder .gitkeep file. Signed-off-by: John McCall <john@overturemaps.org>
1 parent 3036527 commit 8300542

4 files changed

Lines changed: 225 additions & 0 deletions

File tree

.github/actions/.gitkeep

Whitespace-only changes.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Check Linked Issue
2+
3+
A composite GitHub Action that verifies a pull request has at least one linked GitHub issue.
4+
5+
## Explanation
6+
7+
### What it does
8+
9+
This action queries the GitHub GraphQL API for `closingIssuesReferences` on a
10+
pull request. It detects issues linked by:
11+
12+
- Body keywords: `Fixes #123`, `Closes #456`, `Resolves owner/repo#789`
13+
- Manual linking via the GitHub UI sidebar
14+
15+
If no linked issues are found, the step fails with a message guiding the author
16+
to link one.
17+
18+
### Why GraphQL over regex
19+
20+
| Approach | Body keywords | UI-linked issues | Cross-repo refs | Format-proof |
21+
|----------|:---:|:---:|:---:|:---:|
22+
| Regex on PR body ||| Fragile ||
23+
| `closingIssuesReferences` |||||
24+
25+
The GraphQL approach reflects GitHub's actual internal linkage rather than
26+
parsing text, making it reliable across formatting styles and linking methods.
27+
28+
## How-to guides
29+
30+
### Use the reusable workflow (recommended)
31+
32+
The simplest way to adopt this check from any repo in the OvertureMaps
33+
organization. Create a workflow file in your repo:
34+
35+
```yaml
36+
# .github/workflows/check-issue.yml
37+
name: Check Linked Issue
38+
39+
on:
40+
pull_request:
41+
types: [opened, edited, synchronize]
42+
43+
jobs:
44+
check-issue:
45+
uses: OvertureMaps/workflows/.github/workflows/check-issue.yml@main
46+
```
47+
48+
No checkout step is needed — GitHub resolves the reusable workflow and its
49+
actions automatically.
50+
51+
### Use the composite action directly from this repo
52+
53+
If you need to combine this check with other steps in an existing job, check out
54+
the action and reference it locally:
55+
56+
```yaml
57+
# .github/workflows/pr-checks.yml
58+
name: PR Checks
59+
60+
on:
61+
pull_request:
62+
types: [opened, edited, synchronize]
63+
64+
permissions:
65+
contents: read
66+
pull-requests: read
67+
68+
jobs:
69+
validate:
70+
runs-on: ubuntu-latest
71+
steps:
72+
- name: Checkout workflows repo
73+
uses: actions/checkout@v4
74+
with:
75+
repository: OvertureMaps/workflows
76+
sparse-checkout: .github/actions/check-linked-issue
77+
path: .workflows
78+
79+
- name: Check for linked issue
80+
uses: ./.workflows/.github/actions/check-linked-issue
81+
82+
# ... additional steps in the same job
83+
```
84+
85+
### Use the composite action within the workflows repo
86+
87+
When referencing the action from a workflow in this same repository, use a
88+
relative path without a checkout step:
89+
90+
```yaml
91+
steps:
92+
- uses: ./.github/actions/check-linked-issue
93+
```
94+
95+
## Reference
96+
97+
### Permissions
98+
99+
Requires the default `GITHUB_TOKEN` with:
100+
101+
```yaml
102+
permissions:
103+
contents: read
104+
pull-requests: read
105+
```
106+
107+
For private repos, these permissions allow the token to read PR metadata and
108+
query linked issues. Cross-repo issue detection is limited to public repos and
109+
repos within the same organization that the token has access to.
110+
111+
### Inputs
112+
113+
This action has no inputs.
114+
115+
### Outputs
116+
117+
This action has no outputs. It either passes or fails the step.
118+
119+
### Supported trigger events
120+
121+
The action reads `context.payload.pull_request.number`, so it must run on a
122+
`pull_request` or `pull_request_target` event.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
name: Check Linked Issue
3+
description: >
4+
Checks that a pull request has at least one linked GitHub issue via
5+
closingIssuesReferences. Covers 'Fixes #123', 'Closes owner/repo#456',
6+
and issues linked manually through the GitHub UI.
7+
8+
runs:
9+
using: composite
10+
steps:
11+
- name: Check for linked issue via GraphQL
12+
uses: actions/github-script@v7
13+
with:
14+
script: |
15+
const prNumber = context.payload.pull_request.number;
16+
const { owner, repo } = context.repo;
17+
18+
const query = `
19+
query($owner: String!, $repo: String!, $number: Int!) {
20+
repository(owner: $owner, name: $repo) {
21+
pullRequest(number: $number) {
22+
closingIssuesReferences(first: 10) {
23+
nodes {
24+
number
25+
title
26+
state
27+
url
28+
}
29+
}
30+
}
31+
}
32+
}
33+
`;
34+
35+
const result = await github.graphql(query, {
36+
owner,
37+
repo,
38+
number: prNumber
39+
});
40+
41+
const issues =
42+
result.repository.pullRequest.closingIssuesReferences.nodes;
43+
44+
if (!issues || issues.length === 0) {
45+
core.setFailed(
46+
"This PR does not reference any linked issues. " +
47+
"Please link an issue using 'Fixes #123', " +
48+
"'Closes OvertureMaps/other-repo#123', or the GitHub UI " +
49+
"(https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue)"
50+
);
51+
} else {
52+
core.info("Linked issues found:");
53+
issues.forEach(issue => {
54+
core.info(` #${issue.number} - ${issue.title} (${issue.state}) ${issue.url}`);
55+
});
56+
}

.github/workflows/check-issue.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
# Reusable workflow to enforce that PRs have a linked GitHub issue.
3+
#
4+
# Uses the check-linked-issue composite action which queries GraphQL
5+
# closingIssuesReferences to detect linked issues, covering:
6+
# - Body keywords: Fixes #123, Closes #456, Resolves owner/repo#789
7+
# - Issues manually linked via the GitHub UI
8+
#
9+
# Usage: Reference this workflow from any repo in the OvertureMaps organization.
10+
#
11+
# Example (.github/workflows/check-issue.yml):
12+
# name: Check Linked Issue
13+
# on:
14+
# pull_request:
15+
# types: [opened, edited, synchronize]
16+
# jobs:
17+
# check-issue:
18+
# uses: OvertureMaps/workflows/.github/workflows/check-issue.yml@main
19+
#
20+
name: Check Linked Issue
21+
22+
on:
23+
workflow_call:
24+
pull_request:
25+
types:
26+
- opened
27+
- edited
28+
- synchronize
29+
30+
permissions:
31+
contents: read
32+
pull-requests: read
33+
34+
jobs:
35+
check-linked-issue:
36+
name: Check Linked Issue
37+
runs-on: ubuntu-latest
38+
steps:
39+
- name: Checkout workflows repo
40+
uses: actions/checkout@v4
41+
with:
42+
repository: OvertureMaps/workflows
43+
sparse-checkout: .github/actions/check-linked-issue
44+
path: .workflows
45+
46+
- name: Check for linked issue
47+
uses: ./.workflows/.github/actions/check-linked-issue

0 commit comments

Comments
 (0)