Conversation
…ith repo permissions for the repo that uses this action.
…ter stop is called.
There was a problem hiding this comment.
Pull request overview
Adds support for authenticating to GitHub via a GitHub App (instead of requiring a PAT), plus a new option to skip EC2 termination during stop mode.
Changes:
- Add GitHub App inputs (
github-app-private-key,github-app-id) and runtime logic to mint an installation access token whengithub-tokenis not provided. - Add
leave-ec2-instance-runninginput and updatestopflow to optionally skip EC2 termination. - Update runner bootstrap to download the latest
actions/runnerrelease dynamically.
Reviewed changes
Copilot reviewed 7 out of 9 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/index.js | Fetches a GitHub token via GitHub App when PAT is absent; adds “leave instance running” behavior in stop mode. |
| src/gh.js | Implements GitHub App JWT + installation token minting; exports new helper. |
| src/config.js | Adds new action inputs and validation for PAT vs GitHub App configuration. |
| src/aws.js | Switches runner install from pinned version to “latest release” resolution. |
| package.json | Adds axios and jsonwebtoken dependencies for GitHub App auth. |
| package-lock.json | Locks new dependencies and transitive changes. |
| action.yml | Adds new inputs and marks github-token optional. |
| README.md | Updates documentation/inputs table for GitHub App auth and leave-running option. |
Comments suppressed due to low confidence (3)
README.md:208
- Typo/capitalization in this input description: “Github” should be “GitHub”, and “instace” should be “instance”.
| `leave-ec2-instance-running` | Optional. Used only with the `stop` mode. | When `stop` mode is used only deregister the runner in Github and leave the instace running instead of terminating it.<br><br>This is a boolean.<br><br>This is useful if you need to debug failures that requires logging into the instance after the workflow completes. The instance can then be manually stopped or terminated after. |
action.yml:22
- The GitHub App description refers to a “'repo' scope”, but GitHub Apps use permissions (not OAuth scopes). Update wording to avoid misleading configuration instructions.
github-app-private-key:
description: >-
To avoid using a PAT token a Github App with 'repo' scope can be used.
https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps
required: false
action.yml:90
- Typo/capitalization in input description: “Github” should be “GitHub”, and “instace” should be “instance”.
leave-ec2-instance-running:
description: >-
When stop mode is used only deregister the runner in Github and leave the instace running instead of terminating it.
required: false
default: false
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| github-token: | ||
| description: >- | ||
| GitHub Personal Access Token with the 'repo' scope assigned. | ||
| required: true | ||
| required: false |
There was a problem hiding this comment.
This PR updates src/* and adds new runtime dependencies, but the action entrypoint is dist/index.js (built via ncc). Ensure the compiled dist/index.js is regenerated and committed; otherwise the published action won’t include the GitHub App token and leave-instance-running changes.
| const installationsResponse = await axios.get("https://api.github.com/app/installations", { headers }); | ||
| const installations = installationsResponse.data; | ||
|
|
||
| for (const installation of installations) { | ||
| if (installation.account.login.toLowerCase() === config.githubContext.owner.toLowerCase()) { | ||
| installationId = installation.id; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| const accessTokensResponse = await axios.post(`https://api.github.com/app/installations/${installationId}/access_tokens`, {}, { headers }); | ||
| return accessTokensResponse.data.token; |
There was a problem hiding this comment.
These calls hard-code https://api.github.com, which makes the action incompatible with GitHub Enterprise Server / custom API URLs. Prefer using Octokit (already used elsewhere in this module) or deriving the API base URL from the runtime (e.g., the Actions context) instead of hard-coding GitHub.com.
| 'RUNNER_VERSION=$(curl -s "https://api.github.com/repos/actions/runner/releases/latest" | jq -r ".name" | tr -d "v")', | ||
| 'curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz', | ||
| 'tar xzf ./actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz', |
There was a problem hiding this comment.
Same issue as above: this branch also uses jq and .name from the releases API, which can break runner download/untar on AMIs without jq or when the release name differs from the tag. Consider using .tag_name and avoiding jq (or installing it).
| } | ||
|
|
There was a problem hiding this comment.
getGithubToken() can proceed with installationId undefined if no installation matches the repo owner (or if the installations list is empty). That will call the access-token endpoint with undefined and fail with a confusing API error; add an explicit check and throw a clear error when no matching installation is found (and consider surfacing the owner/repo being targeted).
| } | |
| } | |
| if (!installationId) { | |
| const owner = config.githubContext && config.githubContext.owner ? config.githubContext.owner : 'unknown owner'; | |
| const repo = config.githubContext && config.githubContext.repo ? config.githubContext.repo : 'unknown repo'; | |
| throw new Error(`No GitHub App installation found for ${owner}/${repo}`); | |
| } |
|
|
||
| const installationsResponse = await axios.get("https://api.github.com/app/installations", { headers }); | ||
| const installations = installationsResponse.data; | ||
|
|
||
| for (const installation of installations) { | ||
| if (installation.account.login.toLowerCase() === config.githubContext.owner.toLowerCase()) { | ||
| installationId = installation.id; | ||
| break; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
The /app/installations endpoint is paginated; fetching only the first page via a single axios.get can miss the correct installation for GitHub Apps installed on many accounts. Use pagination (or Octokit’s paginate) to ensure the installation search is complete.
| const installationsResponse = await axios.get("https://api.github.com/app/installations", { headers }); | |
| const installations = installationsResponse.data; | |
| for (const installation of installations) { | |
| if (installation.account.login.toLowerCase() === config.githubContext.owner.toLowerCase()) { | |
| installationId = installation.id; | |
| break; | |
| } | |
| } | |
| let page = 1; | |
| let hasNextPage = true; | |
| while (hasNextPage && !installationId) { | |
| const installationsResponse = await axios.get(`https://api.github.com/app/installations?per_page=100&page=${page}`, { headers }); | |
| const installations = installationsResponse.data; | |
| for (const installation of installations) { | |
| if (installation.account.login.toLowerCase() === config.githubContext.owner.toLowerCase()) { | |
| installationId = installation.id; | |
| break; | |
| } | |
| } | |
| const linkHeader = installationsResponse.headers && installationsResponse.headers.link; | |
| if (installationId || !linkHeader || !linkHeader.includes('rel="next"')) { | |
| hasNextPage = false; | |
| } else { | |
| page += 1; | |
| } | |
| } |
| 'RUNNER_VERSION=$(curl -s "https://api.github.com/repos/actions/runner/releases/latest" | jq -r ".name" | tr -d "v")', | ||
| 'curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz', | ||
| 'tar xzf ./actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz', |
There was a problem hiding this comment.
This user-data script now depends on jq being preinstalled on the AMI, but the action doesn’t install it. On many base images this will fail during bootstrapping. Avoid introducing jq as an implicit dependency (or explicitly install it) and add basic error handling for the version resolution step.
| 'RUNNER_VERSION=$(curl -s "https://api.github.com/repos/actions/runner/releases/latest" | jq -r ".name" | tr -d "v")', | ||
| 'curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz', | ||
| 'tar xzf ./actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz', |
There was a problem hiding this comment.
GitHub’s releases API does not guarantee name equals the version tag; using .name can produce a value that doesn’t match the download URL. Use .tag_name (and strip a leading v only) to reliably build the runner tarball URL.
| | `github-token` | Either `github-token` or `github-app-private-key` is Always required. | GitHub Personal Access Token with the `repo` scope assigned. | | | ||
| `github-app-private-key` | Either `github-token` or `github-app-private-key` is Always required. | To avoid using a PAT token a Github App with 'repo' scope can be used (https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps) | | ||
| |`github-app-id` | Required if `github-app-private-key` is used. | If using a Github App to generate the token for runner registration, then the Github App Id is needed. | |
There was a problem hiding this comment.
The Inputs markdown table is malformed here (missing leading/trailing | on some rows), which will break rendering. Reformat the added GitHub App rows so each row has the same number of columns and proper pipe delimiters.
Add functionality to allow a Github App to be used to provide authentication rather than a Github PAT (personal access token).