Skip to content

feat(github): form-encoded deliveries, GitHub App installation tokens, installation events - #1541

Draft
cosmicBboy wants to merge 1 commit into
mainfrom
nielsb/github-plugin-gaps
Draft

feat(github): form-encoded deliveries, GitHub App installation tokens, installation events#1541
cosmicBboy wants to merge 1 commit into
mainfrom
nielsb/github-plugin-gaps

Conversation

@cosmicBboy

Copy link
Copy Markdown
Collaborator

Follow-up to #1540 (Slack): the same exercise for the GitHub plugin — study how the internal agents actually use GitHub, and close the gaps in flyteplugins-github that would block them (or any similar agent) from adopting it.

What the agents actually do with GitHub

  • Cally (oncall_agent/github_app.py) holds no PAT: it authenticates as a GitHub App and mints 1-hour installation tokens for read-only clones, with a GH_TOKEN fallback and a degrade-to-None-with-a-logged-reason contract. Its chat app (opencode_chat/mint_gh_token.py) carries a verbatim copy of that logic, with a docstring noting the copy exists because "the two projects ship as separate code bundles and share no package."
  • Nodey (node_changer_agent.py::get_github_app_token) is a third copy of the same minting flow — with the app id and installation id hardcoded and no error handling — feeding clones, pushes, and gh pr create.
  • Engy (webhook_app.py) runs the one hand-rolled GitHub webhook receiver: issue_comment.created carrying a /swe_agent fix command, plus issues.labeled. Its setup instructions have to warn users to switch the webhook's content type to application/json — because GitHub's Add webhook form defaults to application/x-www-form-urlencoded, which naive receivers (and this plugin, until now) choke on.

Gaps closed

  1. Form-encoded deliveries. parse() only accepted JSON bodies; a webhook left on GitHub's default content type wraps the JSON in a payload= form field and every delivery would have failed. parse now sniffs and unwraps both shapes into the same WebhookEvent — same qualified_type, same dedupe key — so a redelivery on the other content type is still a redelivery. verify already covered both, since the HMAC signs the raw body.
  2. GitHub App installation tokens ([auth] extra, PyJWT[crypto]). mint_installation_token() + clone_url() replace the three per-agent copies: env-var configuration (GITHUB_APP_ID / GITHUB_APP_INSTALLATION_ID / GITHUB_APP_PRIVATE_KEY), explicit-argument override, GITHUB_TOKEN/GH_TOKEN fallbacks for one-secret-at-a-time migration, and Cally's degrade-to-None contract so half-configured deployments keep working unauthenticated instead of crashing.
  3. Installation / InstallationRepositories event constants. Every GitHub App webhook receives these unconditionally, and all three agent deployments authenticate as Apps; they normalize with no resource and dedupe per delivery.

Deliberately not done

  • No workflow_job / deployment_status / status constants — no agent touches them; raw strings still work if one ever does.
  • No slash-command-style parsing: engy's /swe_agent fix trigger is just an issue_comment body, which the existing normalization already carries verbatim in event.payload (verified by a test modeled on engy's exact trigger payload).
  • No PyGithub wrapping beyond the existing review gate — unchanged plugin boundary, now stated as "the parts every GitHub agent otherwise duplicates."

Validation

  • uv run --frozen pytest tests/ -q — 41 passed (new: form-encoded parity with the engy-shaped /swe_agent fix payload, signature-over-form-body, rejection of a form body with no payload field, installation-event dedupe, and Cally's JWT-verifies/fallback/outage/clone-URL matrix against a real RSA key with the HTTP seam monkeypatched).
  • python examples/github_webhooks.py --local now also replays the sample delivery form-encoded and prints it landing on the identical dedupe key.
  • uvx ruff check and ruff format --check clean; conformance test unchanged and passing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NwQixBcyR5va6BC75jaQx3

…llation events

Gaps found by studying how the internal agents (Cally, Nodey, and the
SWE-agent webhook receiver) actually use GitHub:

- parse() only accepted application/json bodies, but GitHub's Add-webhook
  form *defaults* to application/x-www-form-urlencoded, which wraps the
  JSON in a payload= field. The receiver now unwraps both shapes into the
  same WebhookEvent (and the same dedupe key), so a webhook left on the
  default content type works instead of 4xx-ing every delivery.
- Cally's github_app.py, its acknowledged copy in opencode_chat's
  mint_gh_token.py, and Nodey's get_github_app_token are three copies of
  the same GitHub App installation-token minting. mint_installation_token
  and clone_url now live here under the [auth] extra (PyJWT[crypto]),
  with the same degrade-to-None-with-a-logged-reason contract and
  GITHUB_TOKEN/GH_TOKEN fallbacks those agents rely on.
- events gains Installation and InstallationRepositories constants, which
  every GitHub App webhook receives unconditionally.

Tests are modeled on the agents' real payloads (the /swe_agent fix
issue_comment trigger; Cally's JWT/fallback/outage matrix), the example's
--local mode now replays the form-encoded shape too, and the README
documents all three additions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwQixBcyR5va6BC75jaQx3
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