Steal a GitHub installation after confirmation

Status#

Done. The signed confirmation flow, authenticated RPC, atomic claim move, old-connection cleanup, generation-fenced cleanup, dialog, docs, generated API, full local checks, headed preview proof, and green preview rerun are complete. Bugbot's concurrent-steal findings have deterministic regression tests and fixes. Each review update is covered by the PR's required CI checks.

Goal#

When a GitHub App installation is already connected to another Iterate project, let an authorized user move it to the current project after an explicit “are you sure?” confirmation, matching the existing Telegram steal experience.

Decisions and assumptions#

  • A GitHub installation can belong to one Iterate project at a time because its webhooks have one routing destination.
  • The existing GitHub user OAuth check remains the authorization proof. A browser-supplied installation ID is never enough to steal a connection.
  • After GitHub proves the user can access the installation, a conflicting callback mints a short-lived signed confirmation state bound to the installation, target project, and user.
  • The callback redirects to the integrations page with the normal github_installation_already_claimed error plus the signed confirmation state. The page opens an AlertDialog; cancel clears both query parameters.
  • Confirmation is a dedicated project RPC. It accepts only the signed state and derives the confirming user from the authenticated itx principal; callers cannot supply the user identity.
  • Prepare the new project connection before routing moves. Commit the old unclaim and new claim in one directory append, then brick every owner displaced across retries with reason stolen-by-another-project. Give every fresh GitHub claim a fenced connection name so delayed cleanup can only brick the ownership generation it displaced; restore and re-verify the current winner before returning success.
  • Do not name the old project in the result or dialog. It may belong to another organization.
  • Replaying a successfully used confirmation state is idempotent when the installation is already owned by the target project.
  • If the old project releases the installation before confirmation, confirmation connects it as a normal unclaimed installation.

Checklist#

  • Return a signed GitHub steal-confirmation state only after successful GitHub user OAuth proof. The callback mints a short-lived githubInstallationAuthorized state after GitHub enumerates the installation for the signed-in user.
  • Add an authenticated confirmGithubSteal({ state }) project RPC that validates the signed project/user/installation proof. ProjectIntegrationsRpcTarget derives the user from its authenticated principal.
  • Move the directory claim atomically, prepare the new connection first, and dispossess the old project afterward without leaking its identity. confirmGithubSteal batches the unclaim and claim, then records stolen-by-another-project on the old journal.
  • Make confirmation replay safe and handle a claim released between prompt and confirmation. Focused tests cover both outcomes.
  • Show an accessible destructive confirmation dialog on the integrations page; cancel clears the error/state and confirm connects without repeating OAuth. Headed preview-5 verification exercised both buttons and captured the dialog for the PR.
  • Add focused red-green tests for conflict proof, authorization checks, transfer side effects, replay, released-claim behavior, and concurrent steal retries. Eleven GitHub connect tests pass, including an A → target → C → target interleaving and a late-cleanup race after the winner returns success.
  • Update the GitHub integration docs and generated public itx API artifacts. The design doc describes OAuth proof and claim moves; both generated itx API copies and the graph include confirmGithubSteal.
  • Run focused tests and repository pre-PR checks; verify the flow in a headed browser on a preview deployment and add visual proof to the PR. All local workspace checks pass; preview-5 connected and removed a synthetic installation through the deployed UI/RPC, and the PR body includes the screenshot.
  • Move this task to tasks/complete/ and update the PR body when implementation and review are complete. The dated completion file and PR body include the final verification evidence.

Implementation log#

  • 2026-07-23: Production diagnosis found installation 114628444 live on task-demo; the current callback correctly rejected connecting it to misha. That concrete case defines the acceptance flow for this task.
  • 2026-07-23: Added the signed proof, explicit RPC, safe transfer ordering, dashboard dialog, and focused regression coverage. Focused tests and the OS typecheck pass.
  • 2026-07-23: Full workspace tests, typecheck, lint, and formatting pass. Headed preview-5 testing proved cancel and confirm; the synthetic connection was disconnected afterward. The first preview CI run hit an unrelated existing stream-wait flake, so an exact rerun is in progress.
  • 2026-07-23: The exact preview rerun passed all five deployed apps. The PR has no unresolved review threads and its body includes the final screenshot and verification summary.
  • 2026-07-23: Bugbot found that a retry cleaned only its final displaced owner. A deterministic three-project race reproduced the live orphaned secret. The loop now bricks every displaced owner, restores a briefly bricked winner, and verifies ownership again before success.
  • 2026-07-23: Bugbot's follow-up correctly noted that restoring only the secret left the lifecycle journal disconnected and that replay skipped repair. Ownership settlement now restores secret and connected fact together, and both the retry and replay paths use it before returning.
  • 2026-07-23: Bugbot found two surface omissions: __describe() now lists the confirmation RPC, and the controlled GitHub warning handles Escape/close attempts by clearing its signed-state query parameters.
  • 2026-07-23: The dialog now ignores Escape/close requests while confirmation is pending, so it cannot look cancelled while the move continues or discard proof needed to retry a failed move.
  • 2026-07-23: A final high-severity race spec showed stale cleanup bricking a project after it reclaimed and returned success. Fresh GitHub claims now use generation-fenced connection names; delayed cleanup can only disable the exact generation it displaced. All 2,302 OS unit tests pass.

Was this page helpful?