feat(github): form-encoded deliveries, GitHub App installation tokens, installation events - #1541
Draft
cosmicBboy wants to merge 1 commit into
Draft
feat(github): form-encoded deliveries, GitHub App installation tokens, installation events#1541cosmicBboy wants to merge 1 commit into
cosmicBboy wants to merge 1 commit into
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-githubthat would block them (or any similar agent) from adopting it.What the agents actually do with GitHub
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 aGH_TOKENfallback 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."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, andgh pr create.webhook_app.py) runs the one hand-rolled GitHub webhook receiver:issue_comment.createdcarrying a/swe_agent fixcommand, plusissues.labeled. Its setup instructions have to warn users to switch the webhook's content type toapplication/json— because GitHub's Add webhook form defaults toapplication/x-www-form-urlencoded, which naive receivers (and this plugin, until now) choke on.Gaps closed
parse()only accepted JSON bodies; a webhook left on GitHub's default content type wraps the JSON in apayload=form field and every delivery would have failed.parsenow sniffs and unwraps both shapes into the sameWebhookEvent— samequalified_type, same dedupe key — so a redelivery on the other content type is still a redelivery.verifyalready covered both, since the HMAC signs the raw body.[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_TOKENfallbacks for one-secret-at-a-time migration, and Cally's degrade-to-Nonecontract so half-configured deployments keep working unauthenticated instead of crashing.Installation/InstallationRepositoriesevent 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
workflow_job/deployment_status/statusconstants — no agent touches them; raw strings still work if one ever does./swe_agent fixtrigger is just anissue_commentbody, which the existing normalization already carries verbatim inevent.payload(verified by a test modeled on engy's exact trigger payload).Validation
uv run --frozen pytest tests/ -q— 41 passed (new: form-encoded parity with the engy-shaped/swe_agent fixpayload, signature-over-form-body, rejection of a form body with nopayloadfield, 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 --localnow also replays the sample delivery form-encoded and prints it landing on the identical dedupe key.uvx ruff checkandruff format --checkclean; conformance test unchanged and passing.🤖 Generated with Claude Code
https://claude.ai/code/session_01NwQixBcyR5va6BC75jaQx3