Skip to content

feat(bounties): support GITHUB_TOKEN auth for issue comments#489

Merged
ralyodio merged 1 commit into
masterfrom
feat/bounty-github-token
Jun 23, 2026
Merged

feat(bounties): support GITHUB_TOKEN auth for issue comments#489
ralyodio merged 1 commit into
masterfrom
feat/bounty-github-token

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Follow-up to #488. Adds a token auth mode to the bounty→issue comment client so a single GITHUB_TOKEN (a PAT or gh auth token) covers every repo the user can write to with no per-repo GitHub App install.

  • isGitHubConfigured() + resolveToken() — uses GITHUB_TOKEN if set, else the App installation token.
  • Token takes precedence over the App; if neither is set, the comment is silently skipped (unchanged behavior).
  • Comments post as the token's user.
  • .env.example documents GITHUB_TOKEN.

Verified: tsc --noEmit (0), eslint (clean). Pre-commit build skipped (1024MB cap OOMs locally; full next build was verified on #488).

🤖 Generated with Claude Code

Add a token auth mode to the GitHub comment client so a single PAT or
`gh auth token` value covers every repo the user can write to, with no
per-repo GitHub App install. Token takes precedence over the App; if
neither is configured the comment is silently skipped (unchanged).

- github-app.ts: isGitHubConfigured() + resolveToken() (GITHUB_TOKEN
  first, else App installation token)
- document GITHUB_TOKEN in .env.example

Verified: tsc --noEmit (0), eslint (clean). Pre-commit build step skipped
(capped at 1024MB, OOMs locally; production build verified on the prior
merge).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ralyodio ralyodio merged commit bdeed64 into master Jun 23, 2026
@ralyodio ralyodio deleted the feat/bounty-github-token branch June 23, 2026 08:55
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR extends the bounty→GitHub issue comment client to accept a GITHUB_TOKEN (PAT or gh auth token) as an alternative to the GitHub App, avoiding the per-repo installation requirement.

  • Adds isGitHubConfigured() (token or App) and resolveToken() (prefers token, falls back to App installation token) so both postIssueComment and updateIssueComment work with either auth mode.
  • Updates .env.example with clear two-mode documentation; all other callers import only the two high-level functions and are unaffected by the internal refactor.

Confidence Score: 4/5

Safe to merge for the common single-auth-mode case; users with both GITHUB_TOKEN and the GitHub App configured may see silently-dropped comments when the token lacks repo write permission.

The token-precedence logic has no fallback: a set-but-insufficient GITHUB_TOKEN permanently blocks the GitHub App path on a per-call basis. This includes the common case where GITHUB_TOKEN is automatically injected by GitHub Actions, which would shadow a correctly-configured App. The rest of the change — the new exported guard, the .env.example docs, and the two public functions — is straightforward and low-risk.

src/lib/github-app.ts — specifically the resolveToken function and the still-exported isGitHubAppConfigured

Important Files Changed

Filename Overview
src/lib/github-app.ts Adds GITHUB_TOKEN support with a resolveToken helper and isGitHubConfigured guard; token is used unconditionally when set with no fallback to the GitHub App on auth failure
.env.example Documents both GITHUB_TOKEN and GitHub App auth modes with clear precedence rules

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant postIssueComment
    participant resolveToken
    participant GitHubAPI

    Caller->>postIssueComment: postIssueComment(owner, repo, issue, body)
    postIssueComment->>postIssueComment: isGitHubConfigured()?
    alt neither GITHUB_TOKEN nor App configured
        postIssueComment-->>Caller: null (skip)
    else at least one configured
        postIssueComment->>resolveToken: resolveToken(owner, repo)
        alt GITHUB_TOKEN set
            resolveToken-->>postIssueComment: GITHUB_TOKEN (no App fallback)
        else App configured
            resolveToken->>GitHubAPI: "GET /repos/{owner}/{repo}/installation (JWT)"
            GitHubAPI-->>resolveToken: installation id
            resolveToken->>GitHubAPI: "POST /app/installations/{id}/access_tokens"
            GitHubAPI-->>resolveToken: short-lived token
            resolveToken-->>postIssueComment: installation token
        else neither
            resolveToken-->>postIssueComment: null
        end
        alt token is null
            postIssueComment-->>Caller: null
        else token resolved
            postIssueComment->>GitHubAPI: "POST /repos/{owner}/{repo}/issues/{n}/comments"
            GitHubAPI-->>postIssueComment: 201 + comment id (or 4xx)
            postIssueComment-->>Caller: comment id (or null on error)
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant postIssueComment
    participant resolveToken
    participant GitHubAPI

    Caller->>postIssueComment: postIssueComment(owner, repo, issue, body)
    postIssueComment->>postIssueComment: isGitHubConfigured()?
    alt neither GITHUB_TOKEN nor App configured
        postIssueComment-->>Caller: null (skip)
    else at least one configured
        postIssueComment->>resolveToken: resolveToken(owner, repo)
        alt GITHUB_TOKEN set
            resolveToken-->>postIssueComment: GITHUB_TOKEN (no App fallback)
        else App configured
            resolveToken->>GitHubAPI: "GET /repos/{owner}/{repo}/installation (JWT)"
            GitHubAPI-->>resolveToken: installation id
            resolveToken->>GitHubAPI: "POST /app/installations/{id}/access_tokens"
            GitHubAPI-->>resolveToken: short-lived token
            resolveToken-->>postIssueComment: installation token
        else neither
            resolveToken-->>postIssueComment: null
        end
        alt token is null
            postIssueComment-->>Caller: null
        else token resolved
            postIssueComment->>GitHubAPI: "POST /repos/{owner}/{repo}/issues/{n}/comments"
            GitHubAPI-->>postIssueComment: 201 + comment id (or 4xx)
            postIssueComment-->>Caller: comment id (or null on error)
        end
    end
Loading

Comments Outside Diff (1)

  1. src/lib/github-app.ts, line 27-29 (link)

    P2 isGitHubAppConfigured still exported alongside the broader isGitHubConfigured

    isGitHubAppConfigured is now only used internally by isGitHubConfigured, but it remains a public export. Any future caller that reaches for isGitHubAppConfigured as a "is GitHub configured?" guard will miss the GITHUB_TOKEN path entirely, leading to silently skipped comments when only a token is configured. Consider un-exporting it or marking it @internal to reduce the risk of it being misused.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Codex Fix in Claude Code

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(bounties): support GITHUB_TOKEN aut..." | Re-trigger Greptile

Comment thread src/lib/github-app.ts
Comment on lines +39 to +43
async function resolveToken(owner: string, repo: string): Promise<string | null> {
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
if (!isGitHubAppConfigured()) return null;
return installationToken(owner, repo);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 No App fallback when GITHUB_TOKEN is set but fails

resolveToken returns process.env.GITHUB_TOKEN immediately without testing it. If the token is present but lacks write permission on a specific repo (e.g. a read-only PAT, an expired token, or the automatically-injected GITHUB_TOKEN in a GitHub Actions environment — which is scoped only to the current repo), postIssueComment/updateIssueComment will receive a 401/403 from the API and return null, never attempting the GitHub App path even when the App is installed on the target repo. A deployer who sets GITHUB_TOKEN broadly and also has the App configured will silently lose App-based comments on any repo where the PAT is insufficient.

Fix in Codex Fix in Claude Code

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.

1 participant