Skip to content

tokens: OS-14592 use GitHub app for token#2

Open
t-bashir-bs wants to merge 6 commits intomainfrom
OS-14592-use-github-app-for-token
Open

tokens: OS-14592 use GitHub app for token#2
t-bashir-bs wants to merge 6 commits intomainfrom
OS-14592-use-github-app-for-token

Conversation

@t-bashir-bs
Copy link
Collaborator

Add functionality to allow a Github App to be used to provide authentication rather than a Github PAT (personal access token).

Copilot AI review requested due to automatic review settings March 2, 2026 18:45
Copy link

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

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 when github-token is not provided.
  • Add leave-ec2-instance-running input and update stop flow to optionally skip EC2 termination.
  • Update runner bootstrap to download the latest actions/runner release 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.

Comment on lines 14 to +17
github-token:
description: >-
GitHub Personal Access Token with the 'repo' scope assigned.
required: true
required: false
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +50
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;
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +29
'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',
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +48
}

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
}
}
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}`);
}

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +48

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;
}
}

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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;
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +17
'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',
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +17
'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',
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +198
| `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. |
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

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.

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.

3 participants