Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/example/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ GITHUB_APP_ID=
GITHUB_APP_CLIENT_ID=
GITHUB_APP_CLIENT_SECRET=
GITHUB_APP_PRIVATE_KEY=
GITHUB_INSTALLATION_ID=
GITHUB_APP_BOT_NAME=sentry-junior[bot]
GITHUB_APP_BOT_EMAIL=264270552+sentry-junior[bot]@users.noreply.github.com
AI_MODEL=
Expand Down
39 changes: 21 additions & 18 deletions packages/docs/src/content/docs/extend/github-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ narrow write tokens.
connected it.

3. Install the App on the organization or repositories Junior should reach.
4. Copy the App ID, OAuth client ID and secret, installation ID, bot name, and
bot noreply email into your deployment environment.
4. Copy the App ID, OAuth client ID and secret, bot name, and bot noreply
email into your deployment environment.

If Junior should receive events or report PR/issue outcomes, also:

Expand All @@ -84,8 +84,21 @@ denies unsupported writes through the egress allowlist.

Install the App only on repositories where Junior may push. Protect important
branches in GitHub. Across many repositories, users should include `owner/repo`
when the target is not obvious, and those repositories must share the same
installation ID.
when the target is not obvious.

One deployment works with every installation of the App. An App has at most
one installation per account, so Junior uses the installation on the account
that owns the target repository or organization. Junior stores the App's
installations in its database. `installation` and `installation_target`
webhooks keep them current. When Junior finds no stored installation for an
account, it syncs the list from GitHub, at most once a minute. A webhook
delivery uses the installation that sent it.

Reads that name no owner, such as search or GraphQL without an `owner`
variable, use the installation on the account that owns the App. Reads of
accounts without the App use that installation too, so public repositories stay
readable. Writes never use another account's installation. Install the App on
its owner account too.

## Config

Expand Down Expand Up @@ -231,18 +244,6 @@ Names the deployment variable containing the GitHub App private key.

</details>

<details class="plugin-config">
<summary><code>installationIdEnv</code></summary>

Names the deployment variable containing the GitHub App installation ID.

- **Define:** `githubPlugin({ installationIdEnv: "GITHUB_INSTALLATION_ID" })` in `plugins.ts`
- **Default:** `GITHUB_INSTALLATION_ID`
- **Required:** Yes
- **Environment variable:** The variable named by this option

</details>

<details class="plugin-config">
<summary><code>botNameEnv</code></summary>

Expand Down Expand Up @@ -562,8 +563,10 @@ A local `git commit` does not call GitHub. The write happens on push. Grant
permission on the installation, and retry.
- Webhook delivery returns `401`: the App webhook secret does not match
`GITHUB_WEBHOOK_SECRET`, or `X-Hub-Signature-256` is missing.
- Webhook delivery returns `202 Ignored`: wrong installation, or an unsupported
event mapping. Confirm `GITHUB_INSTALLATION_ID` and the event type.
- Webhook delivery returns `202 Ignored`: the payload has no App installation,
or the event mapping is unsupported. Confirm the event type.
- `The GitHub App is not installed on owner`: install the App on that account,
and retry.
- Delivery succeeds but nothing appears in Slack: create a watch
or event automation first. A webhook alone does not create either one.
- Missing repository context: include `owner/repo`, or set a thread default
Expand Down
1 change: 0 additions & 1 deletion packages/junior-evals/src/eval-plugin-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const githubPrivateKey = generateKeyPairSync("rsa", { modulusLength: 2048 })
export function evalGitHubEnv(): Record<string, string> {
return {
GITHUB_APP_ID: "12345",
GITHUB_INSTALLATION_ID: "67890",
GITHUB_APP_PRIVATE_KEY: githubPrivateKey,
GITHUB_APP_BOT_NAME: "junior-eval",
GITHUB_APP_BOT_EMAIL: "12345+junior-eval[bot]@users.noreply.github.com",
Expand Down
3 changes: 2 additions & 1 deletion packages/junior-evals/src/fixture/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,8 @@ function automationRequest(input: AutomationInput): Request {
}
const body = JSON.stringify({
...input.payload,
installation: { id: Number(requiredEnv("GITHUB_INSTALLATION_ID")) },
// Any id works: Junior uses the installation that sent the delivery.
installation: { id: 67890 },
});
const signature = createHmac("sha256", requiredEnv("GITHUB_WEBHOOK_SECRET"))
.update(body)
Expand Down
15 changes: 8 additions & 7 deletions packages/junior-github/SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ Set on the harness host (never in skill files):
- `GITHUB_APP_CLIENT_ID`
- `GITHUB_APP_CLIENT_SECRET`
- `GITHUB_APP_PRIVATE_KEY`
- `GITHUB_INSTALLATION_ID`
- `GITHUB_APP_BOT_NAME` (Git author and committer display name)
- `GITHUB_APP_BOT_EMAIL`

Current limitation: one Junior deployment uses one GitHub App installation ID.
That works for repositories covered by the same installation, but not for repositories that live
under different app installations across orgs/accounts.
One Junior deployment works with every installation of the App. Junior uses the
installation on the account that owns the target repository or organization.
It stores the App's installations in the database. `installation` and
`installation_target` webhooks keep them current. A lookup that finds no row
syncs them from `GET /app/installations`, at most once a minute. Reads that
name no owner, or an account without the App, use the installation on the
account that owns the App. Writes to an account without the App fail.

### Vercel env setup (multiline-safe)

Expand All @@ -61,7 +64,6 @@ For Vercel, prefer CLI file input so newlines are preserved exactly:
vercel env add GITHUB_APP_ID production
vercel env add GITHUB_APP_CLIENT_ID production
vercel env add GITHUB_APP_CLIENT_SECRET production
vercel env add GITHUB_INSTALLATION_ID production
vercel env add GITHUB_APP_BOT_NAME production
vercel env add GITHUB_APP_BOT_EMAIL production
vercel env add GITHUB_APP_PRIVATE_KEY production --sensitive < ./github-app-private-key.pem
Expand Down Expand Up @@ -162,7 +164,7 @@ Use `additionalUserScopes` only when a human-identity integration flow requires
- `user-read` and explicitly human `user-write` operations require the actor, or an explicitly delegated user subject, to authorize the GitHub App through the private OAuth flow. Junior-owned issue, pull request, review, inline review comment, and branch operations do not fall back to user OAuth.
- Headless event turns use the `event` system actor and may receive the same installation grants. This lets Junior respond to subscribed pull request events by committing and pushing fixes without inheriting a subscriber's OAuth credential.
- Git commits use Junior as author and committer. Resolvable human run actors are credited once with `Co-Authored-By` trailers. When the current actor has a linked GitHub identity, Junior prefers a GitHub noreply address so external automation can resolve a login for assignment.
- Installation credential leases are cached on the host by grant name and reused across sandboxes until near expiry. User grants stay actor-scoped. Upstream 403 after injection clears the cached lease, issues a new token, and retries the hop once before recording permission denied.
- Installation credential leases are cached on the host by grant name and target account, and reused across sandboxes until near expiry. User grants stay actor-scoped. Upstream 403 after injection clears the cached lease, issues a new token, and retries the hop once before recording permission denied.
- Sandbox does not receive raw tokens via env; host applies Authorization header transforms for GitHub API and upload calls.

## 4) CLI usage
Expand Down Expand Up @@ -246,7 +248,6 @@ jr-rpc config set github.repo getsentry/junior
- `GITHUB_APP_CLIENT_ID`
- `GITHUB_APP_CLIENT_SECRET`
- `GITHUB_APP_PRIVATE_KEY`
- `GITHUB_INSTALLATION_ID`
- `GITHUB_APP_BOT_NAME`
- `GITHUB_APP_BOT_EMAIL`
2. Confirm the GitHub App is installed on your test repo with the permissions above.
Expand Down
6 changes: 6 additions & 0 deletions packages/junior-github/migrations/0010_installations.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE "junior_github_installations" (
"installation_id" bigint PRIMARY KEY NOT NULL,
"account_login" text,
"updated_at" timestamp with time zone NOT NULL,
CONSTRAINT "junior_github_installations_account_login_unique" UNIQUE("account_login")
);
Loading
Loading