feat(github): share installations with web Cloud Agents - #5978
feat(github): share installations with web Cloud Agents #5978pandemicsyn wants to merge 37 commits into
Conversation
fa6817f to
d70becc
Compare
Code Review SummaryStatus: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe latest commit's three fixes (personal GitHub guard scoping, tRPC output schemas, and association detail resync) are correct; the one remaining finding is the fail-closed candidate guard in Overview
Issue Details (click to expand)WARNING
Note: the inline comment for this finding could not be attached because a stale PENDING review (id 5158007287, commit d70becc) on this PR blocks new review creation. Files Reviewed (8 files)
Fix these issues in Kilo Cloud Previous Review Summaries (14 snapshots, latest commit 242eb98)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 242eb98)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe dedupe fix in the changed commit is correct and the two prior webhook/OAuth-attempt fixes remain verified; the previously reported fail-closed guard in the GitHub bot-link resolver ( Overview
Issue Details (click to expand)WARNING
Files Reviewed (4 files)
Fix these issues in Kilo Cloud Previous review (commit 731e775)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryIncremental review verified both fixes in this commit (webhook receipts are now recorded only on Overview
Issue Details (click to expand)WARNING
Files Reviewed (5 files)
Previous review (commit ec51eee)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryIncremental review of the migration-hardening commit found no new issues in the changed code; the previously reported Overview
Issue Details (click to expand)WARNING
Files Reviewed (7 files)
Previous review (commit af1734d)Status: 1 Issue Found | Recommendation: Address before merge Executive Summary
Overview
Issue Details (click to expand)WARNING
Files Reviewed (5 files)
Inline publication was blocked: this PR has an existing bot pending review (id 5158007287) that this session is not permitted to clear, so the finding is recorded here. Previous review (commit fda919a)Status: 1 Issue Found | Recommendation: Address before merge Executive Summary
Overview
Issue Details (click to expand)WARNING
Files Reviewed (9 files)
Inline publication was blocked: this PR has an existing bot pending review (id 5158007287) that this session is not permitted to clear, so the finding is recorded here. Previous review (commit 4c3d38b)Status: No Issues Found | Recommendation: Merge Executive SummaryRebase onto current main left GitHub-installation review scope unchanged; overlapping files differ only by already-landed main content, with high confidence. Files Reviewed (3 files)
Previous review (commit 98af34e)Status: No Issues Found | Recommendation: Merge Executive SummaryLock-observation tests now match the exact advisory-lock SQL already used by sibling suites; the single-file incremental change looks correct with high confidence. Files Reviewed (1 file)
Previous review (commit fbc73e0)Status: No Issues Found | Recommendation: Merge Executive SummaryLifecycle lock-overlap tests now wait for a blocked owner advisory lock before releasing the holder; the single-file incremental change looks correct with high confidence. Files Reviewed (1 file)
Previous review (commit f0df914)Status: No Issues Found | Recommendation: Merge Executive SummaryRepository refresh now follows the lifecycle lock order and skips deleted or inactive projections; the two-file incremental change looks correct with high confidence. Files Reviewed (2 files)
Previous review (commit 622241e)Status: No Issues Found | Recommendation: Merge Executive SummarySynchronous GitHub lifecycle cleanup now records completed receipts only after required DB work; the incremental simplification looks correct with high confidence. Files Reviewed (8 files)
Previous review (commit cd4e7d7)Status: No Issues Found | Recommendation: Merge Executive SummaryIn-lease GitHub lifecycle receipts now return retryable 503 with Retry-After; the five-file incremental change looks correct with high confidence. Files Reviewed (5 files)
Previous review (commit f1bbbb4)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryFailed GitHub Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
Files Reviewed (5 files)
Previous review (commit 458e94b)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryFailed GitHub Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
Files Reviewed (19 files)
Previous review (commit d70becc)Status: 3 Issues Found | Recommendation: Address before merge Executive SummaryExclusive GitHub uninstall retries can skip cleanup after unlink failures, unpinned runtime auth stays broken after sharing demotion, and the migration rebuilds live Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
Files Reviewed (50 files)
Reviewed by deepseek-v4.1-flash · Input: 62K · Output: 25.5K · Cached: 2M Review guidance: REVIEW.md from base branch |
98af34e to
4c3d38b
Compare
fda919a to
af1734d
Compare
Split 0243 into a pre-flight reconciliation migration and the FK-adding migration: - 0243_reconcile_orphaned_github_installation_id.sql nulls out any platform_integrations.github_installation_id that no longer references an existing github_app_installations row, so the FK added below cannot fail its full-table validation on drift. - 0244_free_thena.sql (renamed from 0243_exotic_triton.sql) now declares the FK with an explicit ON DELETE restrict, matching the repo convention for blocking FKs and the lifecycle already enforced in code: github_app_installations rows are soft-deleted (lifecycle_state/deleted_at), and the only hard-delete path (anonymizeCloudUserData) already guards with a NOT EXISTS check against remaining associations. schema.ts documents the ON DELETE restrict choice inline.
dispatchSharedOnce recorded a completed delivery receipt whenever the shared-installation receipt lookup wasn't missing_canonical, regardless of the dispatched handler's response status. A non-2xx dispatch was therefore marked completed, so a later GitHub redelivery of the same event was short-circuited as a duplicate and could never retry. Only record the receipt when response.ok, leaving non-2xx dispatches unrecorded so GitHub's redelivery can retry them. missing_canonical behavior is unchanged.
beginProviderOAuthAttempt only expired pending/consumed rows whose expires_at was already in the past before inserting the new attempt. An unexpired pending row for the same owner+provider (e.g. an abandoned flow) still collided with the partial unique index on (owner, provider, status = 'pending'), blocking a fresh /connect attempt for up to ATTEMPT_TTL_MS (10 minutes) and surfacing oauth_init_failed. Drop the expires_at filter for pending rows so a new attempt always supersedes an existing pending one for that owner+provider, within the same tx and owner-row lock already held. Consumed rows are still only expired once actually past their TTL, since they don't hold that unique slot. consumeProviderOAuthAttempt's pre-step doesn't insert and matches the row to consume by exact state_hash, so it doesn't share this collision pattern and is unchanged.
results.flatMap(r => r.repositories) in fetchRepositoriesForIntegrations had no dedup after dedupeRepositories was removed, so a corrupted or duplicated cached repositories array on a single installation would surface the same repo twice for that installation. Deliberately keep one entry per (platformIntegrationId, id) rather than deduping by repo id alone: NewSessionPanel already keys and selects repository rows by platform + platformIntegrationId + id, and the existing 'preserves both association choices when two installations expose one repository' test documents that two different installations granting the same repo must both remain selectable, since session start resolves the token/permissions from the chosen installation. Deduping by repo id would silently drop one of those legitimate, functionally distinct entries. Added a test covering the same-installation duplicate case and confirmed the existing cross-installation test is unaffected.
Resolved apps/web/src/components/cloud-agent-next/NewSessionPanel.tsx by taking main's refactored submit-path structure (hoisted creationIntent/creationInput/bitbucketRepo memos, firstChatCreationOperation as state) while restoring PR2's shared-GitHub-installation additions: - RepositoryOption.githubAppType field - githubAppType mapping onto GitHub repo options - explicit "Select the GitHub repository again" guard before creationIntent is used in handleStartSession - githubAppType included in the repo command-item search value - the "Lite" badge for lite GitHub App installations ENVIRONMENT.md auto-merged cleanly.
Three must-fix issues found in code review of the GitHub connections work: 1. NewSessionPanel's GitHub-integration guard was unconditional on selectedGitHubIntegrationId, but that field is only ever populated for organization-scoped repos. Every personal (non-org) user selecting a GitHub repo hit 'Select the GitHub repository again' and could never start a session. Scope the guard to organizationId, matching the sibling conditional already used for creationIntent. 2. Both listGitHubRepositories tRPC output schemas silently stripped githubAppType (and the personal router also stripped platformIntegrationId/platformAccountLogin), making the new 'Lite' badge dead code and contributing to (1). Added the missing fields to both schemas. 3. refreshInstallation stopped syncing account/permissions/scopes/ repository_access/installed_at onto the platform_integrations association row after the shared-installation refactor removed the legacy upsertPlatformIntegrationForOwner call. getInstallation/ listIntegrations read those columns directly off platform_integrations, so GitHubIntegrationDetails kept showing stale data after a refresh. Added syncIntegrationInstallationDetails, scoped by integration id only (safe for shared/multi-owner installations, unlike the removed legacy writer), and wired it into refreshInstallation. Added regression tests for all three: both listGitHubRepositories routers now assert a full repo object round-trips unchanged through the real tRPC output validation, and refreshInstallation's tests assert syncIntegrationInstallationDetails is called with fresh data (and not called on the early-exit failure path).
Outcome
Default-off shared canonical GitHub installations for WEB Cloud Agents with exact tenant-association pinning. Slack shared use remains PR3; Code Reviewer shared routing remains PR4.
Reviewed base: current
mainatcf59d8662499749228164232615da5af0abe5134. Reviewed head:af1734d3ce0b8d11acad16622754be8618603cdb.Exact Association Boundaries
Generic token and Chat SDK webhook paths require canonical
sharing_mode = exclusive. Managed shared lookup requires an exact active association ID. GitHub bot-link signed state carries exactplatformIntegrationId; callback validates association, installation, Standard/Lite identity, canonical lifecycle, local health, and current Kilo owner or organization membership.Predeploy bot-link state without an association ID is accepted only while management and sharing admission remain disabled and only for one healthy canonical-exclusive or true-legacy literal-null app association. Explicit Standard rows require exact signed association identity. Keep features disabled for at least one signed-state TTL after deployment.
Simple Synchronous Lifecycle
Required canonical/local database cleanup runs first and fails non-2xx without recording completion. Standard bot unlink runs second as best-effort alerting. Completed-success receipts dedupe later deliveries; concurrent and crash-window repeats are idempotent. GitHub does not automatically retry failed webhooks, so manual redelivery after database failure is the accepted recovery. No lifecycle queue, cron, outbox, lease, fencing, Retry-After, or automatic retry worker exists.
Repository refresh follows lifecycle lock order and cannot revive terminal state.
Provider Reservation Boundary
Slack, Linear, and Discord OAuth attempts are durable, expiring, owner-row locked, purpose-bound, and single-use. PR3 owns provider-installation generation and lazy recovery.
Migration And Rollout
Generated migration:
0243_exotic_triton.sql. Replacement indexes build concurrently with required transaction boundaries. Before enabling sharing, stale eligible-unlinked must be zero. PR2 adds no canonical reconciliation DML.Verification
The final-rebase PostgreSQL-backed Jest/bootstrap rerun was blocked before test execution because the local OrbStack Docker daemon was unresponsive;
docker info, Compose health, andpnpm test:dbtimed out. This was not an assertion failure. The accepted pre-rebase head had the schema/bootstrap and focused web suites passing.No production queries, repairs, associations, settings, flags, backfills, deployments, or external provider operations were performed.