Skip to content

feat(repo-ui): broken repository recovery (#283 PR 5/7)#292

Open
coji wants to merge 1 commit intofeat/issue-283-uifrom
feat/issue-283-repo-ui
Open

feat(repo-ui): broken repository recovery (#283 PR 5/7)#292
coji wants to merge 1 commit intofeat/issue-283-uifrom
feat/issue-283-repo-ui

Conversation

@coji
Copy link
Copy Markdown
Owner

@coji coji commented Apr 7, 2026

Summary

Issue #283 の実装 stack PR 5/7 — broken state(github_installation_id IS NULL)の repository を救済する UI / mutation / batch CLI を追加。

設計根拠: `docs/rdd/issue-283-multiple-github-accounts.md`
作業計画: `docs/rdd/issue-283-work-plan.md`

依存: #288 (PR 1: schema), #296 (PR 2: query/octokit), #290 (PR 3: webhook/membership), #291 (PR 4: UI)

UX 哲学

PR 4 の Vercel 哲学を継承し、通常時は repository 一覧/詳細に installation 名を出さない。canonical installation を失った broken state の repository だけを救済導線として可視化する。

変更内容

service helper (`app/services/github-app-membership.server.ts`)

  • `fetchEligibleInstallationIds()` 内部 helper 抽出 — `reassignCanonicalAfterLinkLoss` と `reassignBrokenRepository` で eligible link 取得ロジックを共有
  • `reassignBrokenRepository(orgId, repoId, source)` 追加
    • canonical installation を持たない repo に対して membership table から候補を引いて再割当を試みる
    • 結果を discriminated union で返す: `reassigned | no_candidates | ambiguous | not_broken`
    • 1 候補 → `canonical_reassigned` audit log を書く
    • 0 候補 / 2+ 候補 → 戻り値で表現、audit log は書かない(installationId が無いため)
  • `isRepositoryBroken()` を `app/libs/github-account.ts` に追加(client-safe)

route mutation (`app/routes/$orgSlug/settings/repositories._index/`)

  • loader が `integrationMethod` を `'token' | 'github_app' | null` で返す
  • action に `reassignBroken` intent 追加(`source: 'manual_reassign'`)
  • discriminated union を `match.exhaustive` でハンドリング

UI (`+components/repo-columns.tsx`)

  • `NeedsReconnectionBadge` コンポーネント
    • `isRepositoryBroken(repo, integrationMethod)` で判定
    • destructive variant Badge + `AlertTriangleIcon` + tooltip
    • 1-click "Reassign" ボタン (per-row `useFetcher`)
  • 結果別 toast:
    • 成功 → "Repository reassigned to an active installation."
    • 候補 0 → "No active installation can see this repository. Reinstall the GitHub App and try again."
    • 候補 2+ → "Disconnect the unwanted installations to resolve."

batch CLI (`batch/commands/reassign-broken-repositories.ts`)

  • `pnpm batch reassign-broken-repositories [--repository ]`
  • broken repository を全て列挙 → 順次 `reassignBrokenRepository` (`source: 'cli_repair'`)
  • 結果集計と各 repo の状況を consola で出力
  • `match.exhaustive` で網羅性確保

tests

  • `github-app-membership.server.test.ts` に 6 ケース追加:
    • 1 候補 → reassigned + canonical_reassigned event
    • 0 候補 → no_candidates (audit log なし)
    • 2+ 候補 → ambiguous (audit log なし)
    • not_broken(既に canonical あり)
    • suspended link は候補から除外
    • 未初期化 link は候補から除外

満たす受入条件

Stack 位置

```text
PR 1 (#288): schema
└ PR 2 (#296): query/octokit
└ PR 3 (#290): webhook/membership
└ PR 4 (#291): UI
└ [PR 5: repo UI] ← this PR
└ PR 6 (backfill)
└ PR 7 (strict)
```

テスト

  • `pnpm validate` (lint / format / typecheck / build / test 全 345 tests)
  • `reassignBrokenRepository` の 6 ケースをユニットテストでカバー

🤖 Generated with Claude Code

Summary by CodeRabbit

リリースノート

  • 新機能
    • GitHub App統合で接続が切れたリポジトリを自動検出するようになりました
    • リポジトリ設定ページに再接続が必要なリポジトリを示すバッジが表示されるようになりました
    • ページ内から簡単に接続を修復できるようになりました
    • CLIで壊れたリポジトリを一括修復するコマンドが追加されました

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec979750-a42b-40dc-bb8e-2f7b1a758ad7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

ウォークスルー

破損したリポジトリの検出と手動再割り当て機能を導入します。新しい isRepositoryBroken ヘルパーは GitHub App インストール ID が null の場合に識別し、UI に警告バッジを表示し、API から再割り当てアクションを処理し、サービスレイヤーで複数の候補を評価し、CLI で一括操作をサポートします。

変更内容

コホート / ファイル サマリー
破損リポジトリの検出
app/libs/github-account.ts
integrationMethod'github_app'githubInstallationIdnull の場合に破損を判定する isRepositoryBroken 述部を追加。
リポジトリ設定 UI
app/routes/$orgSlug/settings/repositories._index/+components/repo-columns.tsx
integrationMethod パラメータを追加し、破損したリポジトリに対して警告バッジ付きの NeedsReconnectionBadge コンポーネントをレンダリング。バッジは再割り当てアクションのフォームを含む。
ローダーとアクション
app/routes/$orgSlug/settings/repositories._index/index.tsx
並行データフェッチと integrationMethod フィールドをローダーに追加。新しい reassignBroken インテントをアクションに追加し、reassignBrokenRepository を呼び出すロジックと結果ハンドリング(成功/エラー/曖昧なケース)を実装。
GitHub App メンバーシップサービス
app/services/github-app-membership.server.ts
fetchEligibleInstallationIds ヘルパーを追加し、適格な GitHub App インストールをフィルタリング。新しい ReassignBrokenRepositoryResult 型と reassignBrokenRepository 関数を追加し、破損したリポジトリを候補インストール ID に再割り当て(一意の場合)または no_candidates/ambiguous を返す。既存の reassignCanonicalAfterLinkLoss をリファクタリング。
メンバーシップサービステスト
app/services/github-app-membership.server.test.ts
reassignBrokenRepository の新しい describe スイートを追加。成功・候補なし・曖昧・未破損のシナリオ、および除外フィルタリング(中断リンク、未初期化リンク)を検証。
CLI インターフェース
batch/cli.ts
reassign-broken-repositories サブコマンドを登録し、organizationId--repository オプションを受け入れる。
CLI コマンド実装
batch/commands/reassign-broken-repositories.ts
新しい reassignBrokenRepositoriesCommand 関数を実装。組織内のすべての破損リポジトリ(またはフィルタリング時の特定リポジトリ)に対して reassignBrokenRepository を呼び出し、結果をログ出力し、最終サマリーを表示。

シーケンス図

sequenceDiagram
    actor User
    participant UI as リポジトリ設定UI
    participant Action as アクションハンドラー
    participant Service as reassignBrokenRepository
    participant DB as データベース
    
    User->>UI: 「再接続」バッジをクリック
    UI->>UI: フォーム送信<br/>(intent=reassignBroken)
    UI->>Action: POST reassignBroken
    Action->>Service: reassignBrokenRepository呼び出し<br/>(organizationId, repositoryId)
    Service->>DB: githubInstallationId=NULLの<br/>リポジトリを読み込み
    Service->>DB: 適格なGitHub App<br/>インストールをクエリ
    Service->>DB: repositoryInstallationMemberships<br/>をクエリ
    alt 一意の候補が存在
        Service->>DB: githubInstallationIdを更新<br/>(canonical_reassigned イベント)
        Service-->>Action: {status: 'reassigned'}
        Action-->>UI: 成功トースト
    else 複数の候補が存在
        Service-->>Action: {status: 'ambiguous'}
        Action-->>UI: 曖昧なメッセージトースト
    else 候補がない
        Service-->>Action: {status: 'no_candidates'}
        Action-->>UI: 候補なしメッセージトースト
    end
    UI->>User: ユーザーへ結果を表示
Loading

見積もりコード レビュー作業量

🎯 4 (Complex) | ⏱️ ~45 分

関連する可能性のあるプルリクエスト

  • feat: GitHub App 移行 (Phase 2) #245: メイン PR の変更(isRepositoryBroken ヘルパー、UI 再接続バッジ、reassignBrokenRepository + CLI/トランザクショナルロジック)は、GitHub App マイグレーション PR で導入された同じサービス関数ファイルに直接関わる。

  • refactor: action 戻り値型統一・toast 通知・エラーハンドリング整備 #230: 両方の PR はリポジトリ設定ルート(app/routes/$orgSlug/settings/repositories._index/index.tsx)を変更する—メイン PR は新しい reassignBroken インテントと integrationMethod ハンドリングを追加し、検索対象 PR はアクション戻り値形状とエラー/トースト処理をリファクタリングする。

  • docs: add RDD and work plan for issue #283 multi GitHub App installations #287: 関連—メイン PR は(再割り当てロジック、リポジトリ インストール メンバーシップ処理、破損リポジトリ検出/UI/CLI)、複数 GitHub App インストール向け RDD で説明されている設計と作業計画を直接実現するコードを実装する。

🐰 破損したリポジトリ、もうおしまい〜
再接続バッジで、すぐに復活へ
UI から CLI まで、すべての道つながる
GitHub App の絆、ぴったり修復
候補たちを選り分けて、最善を導く✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main objective: implementing broken repository recovery functionality with UI, service mutations, and batch CLI support.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-283-repo-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Owner Author

coji commented Apr 7, 2026

@coji
Copy link
Copy Markdown
Owner Author

coji commented Apr 7, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
batch/commands/reassign-broken-repositories.ts (1)

29-37: トークンベースのリポジトリも取得される可能性があります。

クエリは githubInstallationId IS NULL のリポジトリを全て取得しますが、トークンベースの統合を使用しているリポジトリも該当する可能性があります。reassignBrokenRepositoryno_candidates を返すため動作上の問題はありませんが、大規模な組織では不要な処理が発生する可能性があります。

統合メソッドでフィルタリングするか、ログに not_broken / no_candidates の件数を含めてオペレーターが状況を把握できるようにすることを検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@batch/commands/reassign-broken-repositories.ts` around lines 29 - 37, クエリが
githubInstallationId IS NULL の全リポジトリを取得してトークンベース統合のリポジトリまで含めてしまっているので、tenantDb の
repositories クエリを更新してトークンベースの統合を除外するフィルタ(例: where('integration_method', '!=',
'token') — 実際のカラム名に合わせる)を追加するか、または broken の前後で reassignBrokenRepository
の結果(not_broken / no_candidates)件数を processLogger に出力してオペレーターが状況を把握できるようにしてください;
props.repositoryId 分岐と broken 変数はそのまま使ってください。
app/services/github-app-membership.server.ts (1)

78-84: 存在しないリポジトリに対して not_broken を返す動作について。

リポジトリが存在しない場合に { status: 'not_broken' } を返していますが、これは呼び出し元にとって誤解を招く可能性があります。現在の使用箇所(UI とCLI)ではリポジトリIDは常に有効なソースから取得されるため、実際には問題にならないと思いますが、将来的に not_found のようなステータスを追加することを検討してもよいかもしれません。

現状の実装でも安全なフォールバックとして機能するため、このままでも問題ありません。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/github-app-membership.server.ts` around lines 78 - 84, The
function currently returns { status: 'not_broken' } when the repository lookup
(tenantDb.selectFrom('repositories')... -> repo) yields no row, which can be
misleading; change that branch to return a distinct status such as { status:
'not_found' } (or add a new 'not_found' enum) instead of 'not_broken', and
update any callers/UI/CLI that consume the result of this function in
github-app-membership.server.ts to handle the new 'not_found' case appropriately
(ensure code paths that expect valid repository ids still behave the same).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/services/github-app-membership.server.ts`:
- Around line 78-84: The function currently returns { status: 'not_broken' }
when the repository lookup (tenantDb.selectFrom('repositories')... -> repo)
yields no row, which can be misleading; change that branch to return a distinct
status such as { status: 'not_found' } (or add a new 'not_found' enum) instead
of 'not_broken', and update any callers/UI/CLI that consume the result of this
function in github-app-membership.server.ts to handle the new 'not_found' case
appropriately (ensure code paths that expect valid repository ids still behave
the same).

In `@batch/commands/reassign-broken-repositories.ts`:
- Around line 29-37: クエリが githubInstallationId IS NULL
の全リポジトリを取得してトークンベース統合のリポジトリまで含めてしまっているので、tenantDb の repositories
クエリを更新してトークンベースの統合を除外するフィルタ(例: where('integration_method', '!=', 'token') —
実際のカラム名に合わせる)を追加するか、または broken の前後で reassignBrokenRepository の結果(not_broken /
no_candidates)件数を processLogger に出力してオペレーターが状況を把握できるようにしてください;
props.repositoryId 分岐と broken 変数はそのまま使ってください。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d20ec4d2-f5f0-4061-bcb7-dd54dbc09cbb

📥 Commits

Reviewing files that changed from the base of the PR and between be0b946 and 0e2f6f5.

📒 Files selected for processing (7)
  • app/libs/github-account.ts
  • app/routes/$orgSlug/settings/repositories._index/+components/repo-columns.tsx
  • app/routes/$orgSlug/settings/repositories._index/index.tsx
  • app/services/github-app-membership.server.test.ts
  • app/services/github-app-membership.server.ts
  • batch/cli.ts
  • batch/commands/reassign-broken-repositories.ts

@coji coji force-pushed the feat/issue-283-ui branch from be0b946 to 80555cd Compare April 7, 2026 16:11
@coji coji force-pushed the feat/issue-283-repo-ui branch from 0e2f6f5 to 19f78bb Compare April 7, 2026 16:11
@coji coji marked this pull request as ready for review April 8, 2026 00:34
@coji
Copy link
Copy Markdown
Owner Author

coji commented Apr 8, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coji coji force-pushed the feat/issue-283-ui branch from 80555cd to ad26a55 Compare April 8, 2026 11:04
@coji coji force-pushed the feat/issue-283-repo-ui branch 2 times, most recently from 4e18e33 to d0f0617 Compare April 8, 2026 13:21
@coji coji force-pushed the feat/issue-283-ui branch 2 times, most recently from 9072111 to 5e5aca9 Compare April 8, 2026 14:21
@coji coji force-pushed the feat/issue-283-repo-ui branch from d0f0617 to 26f67f3 Compare April 8, 2026 14:21
@coji coji force-pushed the feat/issue-283-ui branch from 5e5aca9 to 9a2ff19 Compare April 8, 2026 14:50
@coji coji force-pushed the feat/issue-283-repo-ui branch from 26f67f3 to f573893 Compare April 8, 2026 14:50
@coji coji force-pushed the feat/issue-283-ui branch from 9a2ff19 to db2dc3d Compare April 8, 2026 15:15
@coji coji force-pushed the feat/issue-283-repo-ui branch 2 times, most recently from b9ba6fb to ddaae6e Compare April 8, 2026 15:30
@coji coji force-pushed the feat/issue-283-ui branch from db2dc3d to 5d28a9b Compare April 8, 2026 15:30
@coji coji force-pushed the feat/issue-283-repo-ui branch from ddaae6e to 577b1d8 Compare April 8, 2026 15:32
@coji coji force-pushed the feat/issue-283-ui branch from 5d28a9b to 047cd20 Compare April 8, 2026 15:32
@coji coji force-pushed the feat/issue-283-ui branch 2 times, most recently from 5ab3838 to 073e9fe Compare April 8, 2026 15:43
@coji coji force-pushed the feat/issue-283-repo-ui branch from 577b1d8 to 042e55c Compare April 8, 2026 15:43
UX 哲学: 通常時は repository 一覧/詳細に installation 名を出さない (PR 4 の Vercel 哲学を継承)。canonical installation を失って `github_installation_id IS NULL` になった broken state の repository だけを救済導線として可視化する。

repositories list (settings/repositories):
- github_app モードかつ github_installation_id IS NULL の repository に "Needs reconnection" バッジ + tooltip
- 1-click "Reassign" ボタンで canonical reassignment helper を再実行
  - 候補 1 件 → 自動 reassign + バッジ消失 (success toast)
  - 候補 0 件 → "Reinstall the GitHub App and try again" (error toast)
  - 候補 2+ 件 → "Disconnect unwanted installations to resolve" (error toast)
- loader が getIntegration().method を返し、columns が github_app モードのみバッジを出す

mutation (app/services/github-app-membership.server.ts):
- reassignBrokenRepository(orgId, repositoryId, source) helper を追加
  - canonical reassignment helper と同じ eligibility ルール
  - 結果を { reassigned | no_candidates | ambiguous | not_broken } の discriminated union で返す
  - tenant first / shared second + audit log

route mutation wrapper (settings/repositories._index/mutations.server.ts):
- reassignBrokenRepositoryFromUI() で source='manual_reassign' を固定して呼ぶ

batch CLI (batch/commands/reassign-broken-repositories.ts):
- batch/cli.ts に `reassign-broken-repositories <orgId> [--repository <id>]` コマンド追加
- 全 broken repo を順次救済、結果を集計表示
- source='cli_repair'

tests (github-app-membership.server.test.ts):
- reassignBrokenRepository の 6 ケース追加 (1/0/2+ 候補, not_broken, suspended/uninitialized link 除外)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coji coji force-pushed the feat/issue-283-repo-ui branch from 042e55c to bc5e2e9 Compare April 8, 2026 15:57
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