fix(integrations): let users pick which GitHub installation to link - #75667
Conversation
|
😎 This pull request was merged. |
|
Hey @joshsny! 👋 It looks like your git author email on this PR isn't your
You can fix it for this repo with: git config user.email "you@posthog.com"Or set it globally with |
🤖 CI report✅ Bundle size — no changeUncompressed size of every built Total: 65.43 MiB · no change No file changed by more than 1000 B. Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report ✅ Eager graph — within budgetHow much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy
🟢 Largest files eagerly shipped from
|
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 762 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 285.6 KiB | ../node_modules/.pnpm/posthog-js@1.409.4/node_modules/posthog-js/dist/rrweb.js |
| 267.7 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 235.5 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 231.4 KiB | ../node_modules/.pnpm/posthog-js@1.409.4/node_modules/posthog-js/dist/module.js |
| 154.3 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 105.2 KiB | src/lib/api.ts |
| 94.7 KiB | ../packages/quill/packages/quill/dist/index.js |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 90.6 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Toolbar bundle — eager 2.19 MiB within budget
What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.
| Metric | Size | Δ vs base | Budget |
|---|---|---|---|
| Eager (shipped) entry + static imports |
2.19 MiB · 17 files | no change | ████░░░░░░ 38.3% of 5.72 MiB |
| Deferred (lazy) | 2.08 MiB · 33 files | no change | n/a — loads on demand |
Loader dist/toolbar.js |
1.1 KiB | no change | █░░░░░░░░░ 5.8% of 19.5 KiB |
Largest eagerly-shipped chunks
| Size | File |
|---|---|
| 717.7 KiB | dist/toolbar/toolbar-app-RHGTKKCY.css |
| 551.2 KiB | dist/toolbar/chunk-chunk-LMAMJSO4.js |
| 484.6 KiB | dist/toolbar/chunk-chunk-I33HJPDC.js |
| 133.6 KiB | dist/toolbar/chunk-chunk-KNWVTMEH.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-T5KY5WYR.js |
| 71.0 KiB | dist/toolbar/toolbar-app-SA5LAQYV.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-27JL52RE.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-AOSTOGBQ.js |
| 20.9 KiB | dist/toolbar/chunk-chunk-PPPVIVMR.js |
| 12.2 KiB | dist/toolbar/chunk-chunk-PIK3PADE.js |
Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile
✅ Dist folder size — 🔺 +15.2 KiB (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1373.79 MiB · 🔺 +15.2 KiB (+0.0%)
ℹ️ MCP UI apps size — 32 app(s), 17069.0 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 599.6 KB | 187.7 KB |
| action | 457.8 KB | 187.7 KB |
| action-list | 564.4 KB | 187.7 KB |
| cohort | 456.8 KB | 187.7 KB |
| cohort-list | 563.4 KB | 187.7 KB |
| email-template | 456.6 KB | 187.7 KB |
| error-details | 472.4 KB | 187.7 KB |
| error-issue | 457.5 KB | 187.7 KB |
| error-issue-list | 564.3 KB | 187.7 KB |
| experiment | 561.5 KB | 187.7 KB |
| experiment-list | 565.2 KB | 187.7 KB |
| experiment-results | 563.2 KB | 187.7 KB |
| feature-flag | 567.2 KB | 187.7 KB |
| feature-flag-list | 570.9 KB | 187.7 KB |
| feature-flag-testing | 461.0 KB | 187.7 KB |
| insight-actors | 562.2 KB | 187.7 KB |
| invite-email-preview | 456.0 KB | 187.7 KB |
| llm-costs | 559.5 KB | 187.7 KB |
| session-recording | 458.6 KB | 187.7 KB |
| session-summary | 463.9 KB | 187.7 KB |
| survey | 458.4 KB | 187.7 KB |
| survey-global-stats | 562.2 KB | 187.7 KB |
| survey-list | 565.1 KB | 187.7 KB |
| survey-stats | 562.2 KB | 187.7 KB |
| trace-span | 457.2 KB | 187.7 KB |
| trace-span-list | 564.3 KB | 187.7 KB |
| workflow | 457.1 KB | 187.7 KB |
| workflow-list | 563.7 KB | 187.7 KB |
| loops-review | 461.2 KB | 187.7 KB |
| query-results | 746.8 KB | 187.7 KB |
| render-ui | 827.4 KB | 187.7 KB |
| visual-review-snapshots | 461.6 KB | 187.7 KB |
⚠️ Playwright — 1 flaky
🎭 Playwright report · View test results →
- Can delete a person (chromium)
These issues are not necessarily caused by your changes.
Annoyed by this section? Help fix flakies and failures and it will go green!
⚠️ Backend coverage — 95.0% of changed backend lines covered — 4 uncovered
🧪 Backend test coverage
Patch coverage — changed backend lines (products + core): ███████████████████░ 95.0% (90 / 94)
| File | Patch | Uncovered changed lines |
|---|---|---|
posthog/api/integration.py |
83.3% | 1620, 1625 |
posthog/api/github_callback/team_services.py |
90.5% | 435, 547 |
🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 30638269702 -n patch-coverage), or the coverage-data block at the end of this comment.
Per-product line coverage (touched products)
| Product | Coverage | Lines |
|---|---|---|
platform_features |
██░░░░░░░░░░░░░░░░░░ 12.1% |
7 / 58 |
batch_exports |
████████░░░░░░░░░░░░ 39.4% |
8,791 / 22,299 |
demo |
███████████░░░░░░░░░ 56.3% |
1,497 / 2,661 |
warehouse_sources_queue |
████████████░░░░░░░░ 59.2% |
148 / 250 |
data_tools |
██████████████░░░░░░ 70.0% |
63 / 90 |
tasks |
██████████████░░░░░░ 70.1% |
32,888 / 46,903 |
ai_gateway |
███████████████░░░░░ 75.0% |
9 / 12 |
signals |
████████████████░░░░ 81.3% |
24,974 / 30,718 |
cdp |
████████████████░░░░ 82.1% |
3,285 / 3,999 |
data_modeling |
█████████████████░░░ 85.7% |
7,716 / 9,008 |
notebooks |
█████████████████░░░ 86.0% |
7,794 / 9,060 |
wizard |
█████████████████░░░ 86.4% |
1,060 / 1,227 |
actions |
█████████████████░░░ 86.6% |
717 / 828 |
cohorts |
██████████████████░░ 87.8% |
6,181 / 7,040 |
product_tours |
██████████████████░░ 87.9% |
1,303 / 1,482 |
exports |
██████████████████░░ 88.2% |
7,046 / 7,986 |
data_warehouse |
██████████████████░░ 88.4% |
12,192 / 13,798 |
business_knowledge |
██████████████████░░ 89.0% |
4,391 / 4,936 |
engineering_analytics |
██████████████████░░ 89.3% |
6,529 / 7,309 |
conversations |
██████████████████░░ 89.3% |
17,600 / 19,701 |
dashboards |
██████████████████░░ 89.4% |
5,983 / 6,693 |
visual_review |
██████████████████░░ 89.5% |
5,870 / 6,558 |
alerts |
██████████████████░░ 90.3% |
4,482 / 4,966 |
links |
██████████████████░░ 90.6% |
183 / 202 |
streamlit_apps |
██████████████████░░ 90.7% |
2,630 / 2,901 |
mcp_analytics |
██████████████████░░ 90.9% |
3,191 / 3,511 |
error_tracking |
██████████████████░░ 91.0% |
10,928 / 12,010 |
slack_app |
██████████████████░░ 91.1% |
9,664 / 10,610 |
marketing_analytics |
██████████████████░░ 91.2% |
12,092 / 13,265 |
stamphog |
██████████████████░░ 91.3% |
4,505 / 4,936 |
mcp_store |
██████████████████░░ 91.9% |
4,273 / 4,650 |
product_analytics |
███████████████████░ 92.5% |
5,849 / 6,321 |
managed_migrations |
███████████████████░ 92.6% |
1,556 / 1,681 |
early_access_features |
███████████████████░ 92.6% |
1,287 / 1,390 |
notifications |
███████████████████░ 92.6% |
1,017 / 1,098 |
ai_observability |
███████████████████░ 92.8% |
15,618 / 16,821 |
surveys |
███████████████████░ 93.1% |
5,771 / 6,197 |
posthog_ai |
███████████████████░ 93.2% |
1,326 / 1,422 |
web_analytics |
███████████████████░ 93.3% |
14,911 / 15,976 |
approvals |
███████████████████░ 93.3% |
3,437 / 3,682 |
reminders |
███████████████████░ 93.4% |
468 / 501 |
legal_documents |
███████████████████░ 93.8% |
1,628 / 1,736 |
workflows |
███████████████████░ 94.1% |
7,241 / 7,698 |
endpoints |
███████████████████░ 94.2% |
8,655 / 9,192 |
tracing |
███████████████████░ 94.5% |
2,671 / 2,827 |
review_hog |
███████████████████░ 94.6% |
8,101 / 8,563 |
skills |
███████████████████░ 94.6% |
3,158 / 3,337 |
messaging |
███████████████████░ 94.7% |
2,885 / 3,048 |
logs |
███████████████████░ 95.4% |
10,466 / 10,966 |
experiments |
███████████████████░ 95.5% |
26,047 / 27,287 |
growth |
███████████████████░ 96.1% |
3,245 / 3,376 |
annotations |
███████████████████░ 96.2% |
732 / 761 |
revenue_analytics |
███████████████████░ 96.3% |
1,887 / 1,960 |
feature_flags |
███████████████████░ 96.4% |
17,488 / 18,144 |
replay_vision |
███████████████████░ 96.4% |
16,081 / 16,679 |
user_interviews |
███████████████████░ 96.5% |
2,638 / 2,734 |
access_control |
███████████████████░ 96.9% |
870 / 898 |
customer_analytics |
███████████████████░ 97.1% |
10,444 / 10,758 |
warehouse_sources |
███████████████████░ 97.3% |
359,732 / 369,739 |
data_catalog |
████████████████████ 97.8% |
2,589 / 2,648 |
analytics_platform |
████████████████████ 98.0% |
2,153 / 2,197 |
metrics |
████████████████████ 98.2% |
2,491 / 2,536 |
pulse |
████████████████████ 98.4% |
2,017 / 2,049 |
live_debugger |
████████████████████ 99.2% |
613 / 618 |
field_notes |
████████████████████ 99.4% |
158 / 159 |
Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.
eba59c5 to
ce62803
Compare
A GitHub App installs once per org, so a second project reuses an existing installation via "Link existing installation". That button sent no installation_id, hitting the backend auto-resolve path, which raises an ambiguous-installation error when the org has more than one installation — leaving no way to link. Add a github/available_installations endpoint that lists the org's distinct installations, and surface a per-installation picker in the UI that passes the chosen installation_id to the existing github/link_existing endpoint. When the org has a single installation the one-click behavior is unchanged. Generated-By: PostHog Code Task-Id: ae6ad9b6-969c-4e53-9d09-4d2086ea6bf5
PR overviewThis pull request updates the GitHub integration linking flow so users can choose which existing GitHub installation to link to a project. One issue has been addressed, but a cross-project authorization gap remains. A team-scoped API or OAuth token with integration write access to one project can enumerate and link a GitHub installation belonging to another project accessible through the same user account, bypassing the token’s intended scope. Open issues (1)
Fixed/addressed: 1 · PR risk: 7/10 |
Replace the hand-written GitHubAvailableInstallation interface in lib/api with the generated GitHubAvailableInstallationApi, and regenerate the kea-typegen blocks for integrationsLogic. Generated-By: PostHog Code Task-Id: ae6ad9b6-969c-4e53-9d09-4d2086ea6bf5
609a561 to
0d70231
Compare
🦔 Hogbox preview · ✅ ready▶ Open the preview
commit |
Prompt To Fix All With AI### Issue 1
posthog/api/integration.py:1573
**Align the generated response shape**
The `many=True` response annotation generates a paginated API contract, while this action directly returns a bare JSON array without invoking DRF pagination. Generated consumers therefore expect a `results` field that the endpoint does not provide, preventing them from enumerating installations through the generated client.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: 609a561 | Re-trigger Greptile |
There was a problem hiding this comment.
Pull request overview
This PR adds support for linking an existing GitHub App installation when an organization has multiple installations, by introducing a backend “available installations” listing endpoint and updating the GitHub integration UI to let users choose which installation to link.
Changes:
- Added a new
GET integrations/github/available_installationsaction backed bylist_org_github_installations. - Updated GitHub integration UI to show a single “Link existing installation” button (1 install) or a dropdown picker (multiple installs) that passes
installation_id. - Added backend tests covering installation-id disambiguation and installation listing/deduping, plus committed regenerated API artifacts.
Reviewed changes
Copilot reviewed 9 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| services/mcp/src/api/generated.ts | Regenerated MCP API types to include the new GitHub installations schema/operation. |
| products/integrations/mcp/tools.yaml | Registers the new operation in the integrations MCP tools catalog (disabled). |
| products/integrations/frontend/generated/api.ts | Adds generated client for integrations_github_available_installations_list. |
| products/integrations/frontend/generated/api.schemas.ts | Adds generated schemas for GitHubAvailableInstallation* and list params. |
| posthog/api/test/test_integration.py | Adds tests for linking by explicit installation_id and listing org installations. |
| posthog/api/integration.py | Adds GitHubAvailableInstallationSerializer and the new github_available_installations action. |
| posthog/api/github_callback/team_services.py | Implements list_org_github_installations dedupe + exclude-team logic. |
| frontend/src/scenes/integrations/components/Integrations.tsx | Updates GitHub integration setup UI and adds the installation picker component. |
| frontend/src/scenes/integrations/components/Integrations.test.tsx | Adds a Jest test for the multi-installation picker UI behavior. |
| frontend/src/lib/integrations/integrationsLogic.ts | Fetches available installations when no GitHub integration exists; supports linking with installation_id. |
| frontend/src/lib/api.ts | Adds a legacy api.integrations.githubAvailableInstallations helper for the new endpoint. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
👋 Visual changes detected for this PR. Review and approve in PostHog Visual Review If these changes are unexpected, they may be caused by a flaky test or a broken snapshot on master. Don't approve — rerun the job or wait for a fix. |
10 updated Run: 5739f000-0af9-4540-ba57-75d45dfed730 Co-authored-by: joshsny <7135900+joshsny@users.noreply.github.com>
… bare list
Greptile flagged that the many=True response annotation generates a paginated
API contract (a `results` field) while the action returned a bare JSON array,
so generated consumers couldn't enumerate installations. Wrap the list in an
{ installations: [...] } object — matching the github_repos response shape — so
the generated types and MCP tool describe the real payload. CI regenerates the
OpenAPI/MCP types from the serializer.
Generated-By: PostHog Code
Task-Id: ae6ad9b6-969c-4e53-9d09-4d2086ea6bf5
… access Listing and linking an existing GitHub installation only checked org membership plus target-team admin, never whether the requester could access the source project the installation lives on. Because user.teams honours project-based permissioning, an org member who admins the target project but is locked out of a private sibling could enumerate that sibling's installation and link it (and its repositories) into their project. Restrict both list_org_github_installations and link_existing_team_github_integration to source teams in the requester's accessible teams, so the picker never surfaces — and the link endpoint never reuses — an installation the user couldn't otherwise reach. Org admins/owners keep implicit access to all projects, so the legitimate cross-project reuse flow is unchanged. Generated-By: PostHog Code Task-Id: 38be48ca-47ba-42b9-9b97-18e666c96270
…tion-picker Generated-By: PostHog Code Task-Id: 38be48ca-47ba-42b9-9b97-18e666c96270
…aller's access The source-project access check ran after resolving the source across the whole org, so the picker and the link path disagreed: - an installation shared by a private project and an accessible one resolved to the private project's lower-id row and was rejected, despite being offered - auto-resolve counted installations the caller can't see, so the one-click button dead-ended on the ambiguous-installation error the picker exists to avoid Filter candidates by the accessible team set during resolution instead. Also defer the repository cache when listing installations, and call the generated client instead of the legacy api.ts helper.
'Connect organization' was wrong three ways: nothing connects in PostHog when you click it, you can install on a personal account rather than an org, and where you land is GitHub's call. A GitHub App installs at most once per account, so GitHub offers install where it's missing and configure where it isn't, which is why clicking it with one installation just opens that installation's settings. Name the destination instead, and always explain what's on the other side. The supporting line previously only rendered when an existing installation could be linked, so the common case had no explanation at all.
Quill was built for MCP apps and desktop, not the main app, and its components read as out of place next to the surrounding Lemon UI. LemonMenu is what the rest of this scene uses. The AGENTS.md line pointing new menus at quill is stale and is being corrected separately.
🦔 ReviewHog reviewed this pull requestFound 0 must fix, 1 should fix, 3 consider. Published 4 findings (view the review). |
Naming the destination only earns its keep once something is connected. With nothing linked to the project, connecting is exactly what the button does, and the original label says so. Gate the supporting line on the same condition: it explains what 'Manage on GitHub' leads to, which is only a question once the project is connected. That also leaves the unconnected state rendering exactly as it did when the visual baselines were last taken.
|
ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
There was a problem hiding this comment.
ReviewHog Report
Changes
Issues: 4 issues
Files (9)
frontend/snapshots.ymlfrontend/src/lib/integrations/integrationsLogic.tsfrontend/src/scenes/integrations/components/Integrations.tsxposthog/api/github_callback/team_services.pyposthog/api/integration.pyproducts/integrations/frontend/generated/api.schemas.tsproducts/integrations/frontend/generated/api.tsproducts/integrations/mcp/tools.yamlservices/mcp/src/api/generated.ts
…enders Also defer the repository cache blob in the auto-resolve branch, which only reads installation ids.
| installation rather than reinstalling. This backs the picker: when the org has more than | ||
| one installation, the client passes the chosen installation_id to github/link_existing. | ||
| """ | ||
| installations = list_org_github_installations( |
There was a problem hiding this comment.
Medium: Team-scoped tokens can reuse installations outside their scope
The permission layer checks a scoped API or OAuth token only against the project in this URL, but this helper derives source projects from request.user.teams. A holder of an integration:write token scoped to the target project can therefore enumerate sibling installation IDs here and pass one to github/link_existing, whose source lookup uses the same account-wide team set, linking an installation from a project the token cannot access. Pass the authenticator's scoped team IDs into both helpers and intersect them with the user's accessible projects before listing or linking.
| installation rather than reinstalling. This backs the picker: when the org has more than | ||
| one installation, the client passes the chosen installation_id to github/link_existing. | ||
| """ | ||
| installations = list_org_github_installations( |
There was a problem hiding this comment.
Project-scoped tokens can enumerate and reuse sibling-project GitHub installations
list_org_github_installations (and link_existing_team_github_integration) derives the caller's accessible source projects from request.user.teams — the key owner's full access — while scoped-token enforcement (check_team_and_org_permissions in permissions.py) only validates the project in the URL. A personal API key or OAuth token scoped to project A with integration:read can therefore call this new endpoint and enumerate GitHub installation IDs, account names, and source team IDs belonging to sibling project B (which the user can access but the token is not scoped to); with integration:write the same token can link that installation into A via github/link_existing and then read B's installation's repositories through A's github_repos, bypassing the token's project scope.
Prompt To Fix With AI
In `github_available_installations` and `github_link_existing` (posthog/api/integration.py), when the request is authenticated via PersonalAPIKeyAuthentication or OAuthAccessTokenAuthentication with non-empty scoped_teams/scoped_organizations, intersect the accessible team ids passed to `list_org_github_installations` / `_accessible_org_team_ids` with the token's scoped teams so a project-scoped token can only enumerate and reuse installations from projects within its scope. Session-authenticated users keep the current `user.teams` behavior.Severity: medium | Confidence: 85% | React with 👍 if useful or 👎 if not
Problem
A GitHub App installs once per organization, so a second project can't reinstall — it reuses an existing installation via the Link existing installation button. That button posted to
github/link_existingwith an empty payload, which lands in the backend's auto-resolve branch. Auto-resolve only works when the org has exactly one installation: with two or more it can't decide and raises "Your organization has multiple GitHub installations; specify which one to link via installation_id".The backend already supports disambiguating via an
installation_idparameter, but the UI had no way to pick one — so an org with more than one installation had no path to link at all.Changes
GET integrations/github/available_installations(backed bylist_org_github_installations) that returns the org's distinct GitHub installations — one entry perinstallation_id, excluding the current project's own, with account name/type for display.installation_idto the existinggithub/link_existingendpoint.How did you test this code?
Added backend tests in
posthog/api/test/test_integration.py:test_link_existing_with_installation_id_disambiguates_multiple— with multiple org installations, passing an explicitinstallation_idlinks that specific one instead of raising the ambiguous-installation error (the fix path, previously untested).test_list_org_github_installations_dedupes_and_excludes_target_team— the picker source lists one entry per distinct installation, excludes the target team's own install, and carries account metadata.Ran
ruff checkandruff format --checkon the changed Python files (clean). I was not able to run the JS/TS typecheck or the Python test suite in this environment (no dev dependencies installed).🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Investigated from a support question about the "multiple GitHub installations" error, traced it to the empty-payload auto-resolve path, then implemented the picker. Authored with the PostHog Slack app (Claude Code). Followed the repo's DRF and generated-API-type conventions; the corresponding skills (
/improving-drf-endpoints,/adopting-generated-api-types) are repo-local and weren't loadable in this environment, so their documented rules were applied by hand.Follow-up:
frontend/src/lib/api.tsdefines a temporaryGitHubAvailableInstallationinterface. Runhogli build:openapito generateGitHubAvailableInstallationApifrom the new serializer and replace the hand-written type (codegen wasn't runnable here).Created with PostHog from a Slack thread