Skip to content

Instantly share code, notes, and snippets.

@eonist
Created February 8, 2026 07:30
Show Gist options
  • Select an option

  • Save eonist/cdeb7034a8f6c4c2c237b3b8c3dbd53d to your computer and use it in GitHub Desktop.

Select an option

Save eonist/cdeb7034a8f6c4c2c237b3b8c3dbd53d to your computer and use it in GitHub Desktop.
oauth in GitHub action for cli
Running an OAuth-based CLI app in GitHub Actions is tricky because OAuth flows (device flow or browser-based) require interactive user input, which isn't available in a headless CI environment. Here are several strategies to work around this.
## Bypass OAuth with a Token Environment Variable
Most well-designed CLI tools that use OAuth also support reading a pre-existing token from an environment variable or config file. For example, GitHub's own `gh` CLI checks for `GH_TOKEN` or `GITHUB_TOKEN` before triggering the OAuth flow. If your CLI app supports this pattern, the simplest approach is: [cli.github](https://cli.github.com/manual/gh_auth_login)
```yaml
jobs:
my-job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: my-cli-app do-something
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
The `GITHUB_TOKEN` is automatically generated by GitHub Actions for each workflow run and is scoped to the repository. If your CLI needs broader permissions (e.g., access to other repos or orgs), use a **Personal Access Token (PAT)** stored as a repository secret instead. [docs.github](https://docs.github.com/actions/reference/authentication-in-a-workflow)
## If You Control the CLI App
If you're the author of the CLI, the best practice is to add a code path that **skips the interactive OAuth flow** when a token is already available. A common pattern:
1. Check for an environment variable (e.g., `GITHUB_TOKEN` or `MY_CLI_TOKEN`) at startup.
2. If the variable is set, use it directly as the Bearer token for API requests, bypassing the device/browser flow entirely.
3. If not set, fall back to the interactive OAuth flow for local development.
```python
import os
token = os.environ.get("GITHUB_TOKEN")
if not token:
token = run_oauth_device_flow() # interactive
# Use token for API calls
headers = {"Authorization": f"Bearer {token}"}
```
This is the pattern GitHub's own CLI uses — it checks environment variables first and only prompts for login if none are found. [github](https://github.com/cli/cli/issues/3799)
## Use a GitHub App for Fine-Grained Access
If your use case requires acting as a specific identity with specific permissions (rather than the workflow's default token), you can create a **GitHub App** and generate installation tokens in the workflow: [dev](https://dev.to/dtinth/authenticating-as-a-github-app-in-a-github-actions-workflow-27co)
1. Register a GitHub App with the needed permissions.
2. Store the App ID and private key as repository secrets.
3. Use an action like `tibdex/github-app-token` or `actions/create-github-app-token` to generate a short-lived installation token at runtime.
```yaml
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- run: my-cli-app do-something
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
```
## Pre-Authenticate and Cache the Token
If you absolutely cannot modify the CLI and it doesn't support environment-variable-based auth, you can:
- **Pre-generate an OAuth token** locally using the device flow and store the resulting access token as a GitHub Actions secret. [stackoverflow](https://stackoverflow.com/questions/68005219/web-browser-authentication-in-github-actions-for-oauth-token)
- In the workflow, write that token to wherever the CLI expects to find its cached credentials (e.g., a config file like `~/.config/my-cli/token`).
```yaml
steps:
- run: |
mkdir -p ~/.config/my-cli
echo "${{ secrets.MY_CLI_OAUTH_TOKEN }}" > ~/.config/my-cli/token
- run: my-cli-app do-something
```
The downside is that OAuth tokens can expire (GitHub user access tokens expire after 8 hours by default if using device flow with expiration enabled), so you'd need to handle refresh tokens or regenerate periodically. [stackoverflow](https://stackoverflow.com/questions/77385245/how-to-use-refresh-tokens-in-device-flow)
## Recommended Approach
| Scenario | Best Option |
|---|---|
| You control the CLI code | Add env var support to skip OAuth [cli.github](https://cli.github.com/manual/gh_auth_login) |
| CLI already reads `GITHUB_TOKEN` | Pass `${{ secrets.GITHUB_TOKEN }}` directly [docs.github](https://docs.github.com/actions/reference/authentication-in-a-workflow) |
| Need cross-repo or org access | Use a PAT stored as a secret [stackoverflow](https://stackoverflow.com/questions/68005219/web-browser-authentication-in-github-actions-for-oauth-token) |
| Need fine-grained, rotating credentials | Use a GitHub App installation token [dev](https://dev.to/dtinth/authenticating-as-a-github-app-in-a-github-actions-workflow-27co) |
| Can't modify CLI at all | Pre-cache an OAuth token as a secret |
The most robust and maintainable solution is to have the CLI support token-based auth via environment variables, which is the standard pattern for headless/CI environments. [polpiella](https://www.polpiella.dev/how-to-use-the-github-cli-from-github-actions-workflows)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment