Skip to content

refactor(host-service): adopt upstream #3697 split workspace-creation router (closes #415)#417

Merged
MocA-Love merged 2 commits intomainfrom
upstream/pr12-split-workspace-creation
Apr 24, 2026
Merged

refactor(host-service): adopt upstream #3697 split workspace-creation router (closes #415)#417
MocA-Love merged 2 commits intomainfrom
upstream/pr12-split-workspace-creation

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Apr 24, 2026

Summary

Issue #415 対応: upstream commit 167542eb4 (#3697)workspace-creation.ts split refactor を fork に取り込み。workspace-creation.ts1758 行 → 28 行 facade に縮小、機能は 24 個の procedure/shared ファイルに分散。

behind = 0 達成 (計画後 upstream の残 1 commit を取り込み)。

Fork 独自拡張を全て新 split 構造に再配置

拡張 配置先
baseBranchSource (picker hint) schemas.ts + procedures/create.ts + procedures/adopt.ts
PR checkout mode (derivePrLocalBranchName, detached worktree, gh pr checkout --force, pre-existing/closed/merged warnings) procedures/checkout.ts
FORK NOTE コメント群 関連 procedure に復元
GitHub link command wiring (gh issue develop, gh pr create) 各 procedure
normalize-github-query.ts (fork-only URL normalization) 維持
applyAiWorkspaceRename + updateNameFromHost no-op detection utils/ai-workspace-names.ts (PR #9 で fork main から欠落していた関数を本 PR で供給)

重要な設計判断

  • worktree path 方針: <repoPath>/.worktrees/<branch> を維持。upstream の ~/.superset/worktrees/<projectId>/<branch> は採用しない (既存 workspace / file watcher / migration 互換性のため)
  • v2-workspace への updateNameFromHost 追加: packages/trpc/src/router/v2-workspace/v2-workspace.ts に JWT procedure 追加。expectedCurrentName WHERE guard で並行 user rename の race を検出、no-op 時は既存 row を返して host-service で git branch rollback 可能に
  • @mastra/core 1.25.0 依存追加: packages/host-service/package.json に追加 (applyAiWorkspaceRename が Agent 使用)。packages/chat が同 version を持つため workspace 共有で解決

新 split 構造

packages/host-service/src/trpc/router/workspace-creation/
├── workspace-creation.ts  ← 28 行 facade (mergeRouters のみ)
├── index.ts
├── normalize-github-query.ts (+ test, fork-only 維持)
├── schemas.ts (全 procedure の zod schemas)
├── procedures/ (11 ファイル)
│   ├── adopt.ts
│   ├── checkout.ts
│   ├── create.ts
│   ├── generate-branch-name.ts
│   ├── get-context.ts
│   ├── get-github-issue-content.ts
│   ├── get-github-pull-request-content.ts
│   ├── get-progress.ts
│   ├── index.ts
│   ├── search-branches.ts
│   ├── search-github-issues.ts
│   └── search-pull-requests.ts
├── shared/ (11 ファイル)
│   ├── branch-search.ts
│   ├── finish-checkout.ts
│   ├── git-config.ts
│   ├── local-project.ts
│   ├── progress-store.ts
│   ├── project-helpers.ts
│   ├── setup-terminal.ts
│   ├── start-point.ts
│   ├── types.ts
│   └── worktree-paths.ts
└── utils/ (fork utils + 新規補完)
    ├── ai-branch-name.ts (fork-only)
    ├── ai-workspace-names.ts (新規、applyAiWorkspaceRename 復元)
    ├── exec-gh.ts (fork-only)
    ├── list-branch-names.ts (新規)
    ├── pr-branch-name.ts (fork-only, + test)
    ├── resolve-start-point.ts (fork-only, + test)
    └── sanitize-branch.ts (fork-only)

Fork 固有機能ヘルスチェック

全維持 (実体は新 split ファイル内に配置):

  • 19 tRPC プロシージャ (github-extended 配下)
  • ansi_up, @vscode/ripgrep, @xyflow/react 依存
  • TERMINAL_OPTIONS, SUPERSET_WORKSPACE_NAME, moonshot-ai.kimi-code
  • desktop version 1.5.10, dmg.size="4g"
  • TiptapPromptEditor, v1MigrationState

Test plan

  • bun install 正常完了
  • bun run typecheck グリーン (27/27)
  • bun run lint グリーン
  • desktop dev 起動確認 (workspace create/adopt/checkout フロー)
  • PR checkout mode 動作 (gh pr checkout + fork/upstream remote 区別)
  • base branch 選択 picker で baseBranchSource が host-service 側に正しく伝搬
  • AI workspace rename: cloud + local + git branch が lockstep
  • no-op rollback: expectedCurrentName mismatch 時に git rename が rollback される

関連

Closes #415

Summary by CodeRabbit

ワークスペース作成機能の拡張

  • New Features

    • ワークスペース作成、採用、チェックアウト機能を追加
    • ブランチまたはプルリクエストからワークスペースを作成可能に
    • AI駆動のワークスペース名自動生成機能を実装
    • GitHub関連の検索・コンテンツ取得機能を統合
    • 進捗追跡とターミナルセッション管理を追加
  • Dependencies

    • @mastra/core@1.25.0を追加

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Warning

Rate limit exceeded

@MocA-Love has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 26 minutes and 8 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 26 minutes and 8 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5cd92ee6-eaab-4b9d-8137-8eb4065d9042

📥 Commits

Reviewing files that changed from the base of the PR and between 6eb0a81 and 9abf35f.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (26)
  • packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/generate-branch-name.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/get-context.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/get-github-issue-content.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/get-github-pull-request-content.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/get-progress.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/index.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/search-branches.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/search-github-issues.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/search-pull-requests.ts
  • packages/host-service/src/trpc/router/workspace-creation/schemas.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/git-config.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/local-project.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/progress-store.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/project-helpers.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/setup-terminal.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/start-point.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/types.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts
  • packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
  • packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
  • packages/trpc/src/router/v2-workspace/v2-workspace.ts
📝 Walkthrough

Walkthrough

packages/host-serviceworkspace-creation ルーターを、約2000行の単一ファイルから24個の手続き・共有ユーティリティファイルに分割するリファクタリング。スキーマ定義、Git操作ヘルパー、進捗追跡、GitHub統合を段階化し、ルータ本体は軽量ファサードに簡素化。新たなv2ワークスペースエンドポイントを追加し、ホストからのAI駆動リネームをサポート。

Changes

Cohort / File(s) Summary
依存関係更新
packages/host-service/package.json
@mastra/core@1.25.0 を新規追加。
ワークスペース作成スキーマ
packages/host-service/src/trpc/router/workspace-creation/schemas.ts
プロジェクトコンテキスト取得、ブランチ検索、ブランチ名生成、進捗照会、ワークスペース作成・チェックアウト・採択、GitHub検索・コンテンツリクエストの Zod 入力スキーマ及び GitHub issue/PR コンテンツ型を定義。
ワークスペース採択手続き
packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts
登録済みワークツリーを検証して、既存ローカルプロジェクトをクラウドワークスペースに関連付ける tRPC ミューテーション。ホスト登録、クラウド作成、ローカルDB永続化、失敗時ロールバック を実装。
ワークスペースチェックアウト手続き
packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts
PR番号またはブランチ指定によるワークスペース設定の tRPC ミューテーション。進捗更新、git ワークツリー作成、自動プッシュ設定、最終化を調整。
ワークスペース作成手続き
packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts
新規ブランチ・ワークツリー生成、スタートポイント解決、クラウド・ローカル永続化、オプションの AI 駆動リネーム及びセットアップターミナル起動を含む主要作成フロー。
ブランチ名生成・コンテキスト取得・進捗・検索手続き
packages/host-service/src/trpc/router/workspace-creation/procedures/generate-branch-name.ts, get-context.ts, get-progress.ts, search-branches.ts
AI プロンプトからブランチ名生成、ローカルリポジトリ存在検証、進捗ステップ照会、ページネーション付きブランチリスト取得(リモートフェッチ最適化、ワークスペース状態フラグ付き)。
GitHub issue/PR コンテンツ取得手続き
packages/host-service/src/trpc/router/workspace-creation/procedures/get-github-issue-content.ts, get-github-pull-request-content.ts, search-github-issues.ts, search-pull-requests.ts
GitHub CLI または Octokit 経由で issue/PR メタデータを取得・正規化するクエリ手続き。直接検索とリポジトリ検索に対応。
手続き一覧
packages/host-service/src/trpc/router/workspace-creation/procedures/index.ts
すべての workspace-creation 手続きを中央エクスポート。
共有ユーティリティ:ブランチ検索・進捗
packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts, progress-store.ts
Base64url カーソルペジネーション、リモートフェッチ TTL ゲート、ワークツリー・ブランチ検索ロジック。メモリ内進捗追跡(ステップ状態遷移、5分間のスウィープ)。
共有ユーティリティ:ローカルプロジェクト・Git設定・ワークツリーパス
packages/host-service/src/trpc/router/workspace-creation/shared/local-project.ts, git-config.ts, worktree-paths.ts
プロジェクト DB クエリ、自動プッシュ設定の有効化、パストラバーサル検証付きワークツリーパス解決。
共有ユーティリティ:チェックアウト完了・セットアップターミナル・スタートポイント
packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts, setup-terminal.ts, start-point.ts
ワークスペース最終化(ホスト登録・クラウド作成・ローカルDB・オプションセットアップスクリプト実行)。スタートポイント hint ビルダー。
共有ユーティリティ:型・ヘルパー
packages/host-service/src/trpc/router/workspace-creation/shared/types.ts, project-helpers.ts
Git クライアント・ターミナル・ブランチ行・チェックアウト結果型。GitHub リポジトリ解決、プロジェクトセットアップエラー構築。
ユーティリティ:AI リネーム・ブランチリスト
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts, list-branch-names.ts
LLM プロンプトから構造化ワークスペースメタデータ(タイトル + kebab-case ブランチ)を生成・適用。Git リモートトラッキング・ローカルブランチを集約。
ワークスペース作成ルータ本体
packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
2000行超の実装を削除し、./procedures からの再エクスポートで軽量ファサード化(-1754 行)。
V2 ワークスペース更新エンドポイント
packages/trpc/src/router/v2-workspace/v2-workspace.ts
updateNameFromHost JWT 認証エンドポイントを追加。ホスト AI リネーム指令をサポート、並行実行ガード (expectedCurrentName) で競合回避。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

大規模な構造的リファクタリング(24 ファイル新規作成)、多様な関心事の分散(手続き・スキーマ・共有ユーティリティ・型)、複雑な制御フロー(進捗追跡、ワークツリー・Git操作、クラウド・ローカルの同期化、ロールバック)が含まれるため。

Possibly related PRs

Poem

🐰 ツリーは分かれ、道は増えて
手続き 24、光みなぎり
共有なる根から新芽萌え
チェックアウトして、ワークスペース咲く ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adopting upstream #3697 to refactor the workspace-creation router by splitting the monolithic file into modular procedures and shared utilities.
Description check ✅ Passed The PR description is comprehensive, with clear sections (Summary, fork extensions, design decisions, new structure, test plan, and related issues) following the template structure.
Linked Issues check ✅ Passed The PR successfully implements Plan A from Issue #415: splits workspace-creation.ts (1758 lines → 28-line facade), redistributes logic into 24 supporting files (schemas.ts, procedures/, shared/), and preserves all fork-specific extensions (baseBranchSource, PR checkout mode, applyAiWorkspaceRename, updateNameFromHost no-op detection).
Out of Scope Changes check ✅ Passed All changes are in scope: workspace-creation refactoring per Issue #415, addition of updateNameFromHost to v2-workspace router for concurrent rename detection, and @mastra/core 1.25.0 dependency addition for AI Agent support. No extraneous modifications detected.

✏️ 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 upstream/pr12-split-workspace-creation

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6eb0a815c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +238 to +240
.update(v2Workspaces)
.set(data)
.where(eq(v2Workspaces.id, workspace.id))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make expectedCurrentName guard atomic in updateNameFromHost

The race-protection for expectedCurrentName is currently split into a read-then-update sequence, but the UPDATE only filters by workspace id. If a user renames the workspace after the initial findFirst check and before this UPDATE executes, the AI rename still overwrites that newer user value, which is the exact race this endpoint is meant to prevent. Include expectedCurrentName in the UPDATE predicate (or use a transaction/locking strategy) so the guard is enforced atomically.

Useful? React with 👍 / 👎.

Comment on lines +215 to +218
ctx.db
.update(workspaces)
.set({ branch: deduped })
.where(eq(workspaces.id, workspaceId))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep worktree path consistent after AI branch rename

After applyAiWorkspaceRename renames the git branch, only workspaces.branch is updated locally while worktreePath is left at the old .worktrees/<old-branch> directory. New workspace creation derives paths from branch names (safeResolveWorktreePath), so reusing the old branch name later can collide with that still-existing directory and fail checkout/create despite no branch-name collision. The rename flow should either move the worktree directory too or account for occupied worktree paths when choosing/deduplicating branch names.

Useful? React with 👍 / 👎.

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.

Actionable comments posted: 7

🧹 Nitpick comments (13)
packages/host-service/src/trpc/router/workspace-creation/shared/types.ts (1)

5-30: オブジェクト型は interface で定義する規約に合わせることを推奨します。

TerminalDescriptor / BranchRow / CheckoutResult はいずれもプレーンなオブジェクト形なので、規約に従い interface 化するのが望ましいです。GitClientAwaited<ReturnType<...>> による派生型なので type のままで問題ありません。

♻️ 推奨パッチ
-export type TerminalDescriptor = {
-	id: string;
-	role: string;
-	label: string;
-};
+export interface TerminalDescriptor {
+	id: string;
+	role: string;
+	label: string;
+}

-export type BranchRow = {
+export interface BranchRow {
 	name: string;
 	lastCommitDate: number;
 	isLocal: boolean;
 	isRemote: boolean;
 	recency: number | null;
 	worktreePath: string | null;
 	// True when a workspaces row exists for this (project, branch) on this
 	// host. A worktree can exist on disk without one (orphan); the Worktree
 	// tab distinguishes Open (hasWorkspace) from Create (orphan adopt).
 	hasWorkspace: boolean;
 	isCheckedOut: boolean;
-};
+}

-export type CheckoutResult = {
-	workspace: { id: string };
-	terminals: TerminalDescriptor[];
-	warnings: string[];
-	alreadyExists?: false;
-};
+export interface CheckoutResult {
+	workspace: { id: string };
+	terminals: TerminalDescriptor[];
+	warnings: string[];
+	alreadyExists?: false;
+}

As per coding guidelines: 「Prefer interface for defining object shapes in TypeScript」。

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

In `@packages/host-service/src/trpc/router/workspace-creation/shared/types.ts`
around lines 5 - 30, The exported object-shaped types TerminalDescriptor,
BranchRow, and CheckoutResult should be converted from type aliases to
interfaces to follow the project's TS convention; update declarations for
TerminalDescriptor, BranchRow, and CheckoutResult to use interface instead of
type (preserving all properties, optional flags like alreadyExists?: false, and
array/primitive types) while leaving any non-object derived types (e.g.,
GitClient-related Awaited/ReturnType usage) unchanged.
packages/host-service/src/trpc/router/workspace-creation/utils/list-branch-names.ts (1)

33-35: エラーを完全に無視せず、ログに残すことを推奨します。

git raw の失敗や入力パース失敗の両方を [] で吸収しており、AI ブランチ名生成の入力が欠落しても静かに進行します。戻り値のフォールバックは維持しつつ、原因追跡のために console.warn を 1 行追加するだけで運用時の切り分けが楽になります。

🔧 推奨パッチ
-	} catch {
-		return [];
-	}
+	} catch (err) {
+		console.warn(
+			"[workspaceCreation.listBranchNames] failed to enumerate refs",
+			{ repoPath, err },
+		);
+		return [];
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/list-branch-names.ts`
around lines 33 - 35, The catch block in list-branch-names.ts is silently
swallowing errors (it currently reads "} catch { return []; }"); update the
catch to capture the thrown error (e.g., "catch (err)") and add a single-line
console.warn or logger call that includes contextual text and the error object
(for example: "console.warn('listBranchNames: failed to list branches', err)"),
then still return [] so the fallback behavior remains unchanged.
packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts (1)

111-140: クラウドロールバック失敗時にゾンビワークスペースが残り得ます(軽微)。

ローカル DB 挿入失敗 → クラウド v2Workspace.delete 失敗という二重障害時、catch 内で警告ログのみ残して元エラーを throw するため、クラウド側に孤立 workspace 行が残存する可能性があります。現状の運用として許容範囲ですが、将来的には再試行キュー/リコンシリエーションジョブに回す、あるいはレスポンス中で cleanupError をクライアントに通知するなどの拡張を検討する余地があります。

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

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts`
around lines 111 - 140, The catch block for the local DB insert can leave a
zombie cloud workspace if ctx.api.v2Workspace.delete.mutate fails; capture the
cleanup error (cleanupErr) when delete.catch runs, log it with context, and
include that cleanupErr information in the TRPCError you throw (e.g., append to
the message or set as the error cause/metadata) so callers are aware a cloud
rollback also failed; also consider enqueuing the failed cloud cleanup for retry
if you have a retry/reconciliation mechanism (pass cloudRow.id and cleanupErr to
that queue) and ensure rollbackWorktree and clearProgress are still executed.
packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts (2)

214-230: Promise チェーン .catch()await の混在

await ctx.api.v2Workspace.create.mutate(...).catch(async (err) => { ...; throw err; }) は動作はしますが、同ファイル内の他箇所(Lines 198–212 の ensureV2Host 等)は try/catch スタイルを採用しており不統一です。コーディングガイドラインの async/await 推奨方針にも沿うので、統一するとレビュアビリティが上がります。

As per coding guidelines: "Use async/await for promises in TypeScript/JavaScript".

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

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts`
around lines 214 - 230, Replace the mixed await/.catch pattern around
ctx.api.v2Workspace.create.mutate with a try/catch block to match the
surrounding code style: move the awaited call to inside try, and in the catch
call clearProgress(input.pendingId), await rollbackWorktree(), log the error
(same message using console.error with the err and context like
"[workspaceCreation.create] v2Workspace.create failed"), then rethrow the error
so callers still see it; reference the cloudRow assignment,
ctx.api.v2Workspace.create.mutate, clearProgress, rollbackWorktree, and
input.pendingId when making the change.

197-270: createfinishCheckout 間でホスト登録・クラウド作成・ローカル永続化ロジックが重複

ensureV2Host → v2Workspace.create → ローカル workspaces insert → 各段のロールバックという同一シーケンスが create.ts (Lines 197–270) と finish-checkout.ts にほぼコピペで存在します。今回は上流 split の忠実移植を優先してよいですが、将来的には共有ヘルパ(例: persistWorkspace(ctx, { cloudInput, localInput, rollbacks }))に集約することでロールバック漏れのリスクを減らせます。

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

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts`
around lines 197 - 270, The host registration -> cloud workspace create -> local
persist -> per-step rollback sequence is duplicated between create.ts and
finish-checkout.ts; extract that flow into a shared helper (e.g.,
persistWorkspace) and call it from both places so rollback logic is centralized.
Specifically, encapsulate the calls to ctx.api.device.ensureV2Host.mutate
(ensureV2Host), ctx.api.v2Workspace.create.mutate (v2Workspace.create) and the
local DB insert (ctx.db.insert(workspaces).values(...).run()) into the helper,
accept inputs for cloud/local payloads and a rollback handler list, perform
try/catch around each step inside the helper, run registered rollback actions on
failure, and surface a uniform error (TRPCError) back to callers; then replace
the inline sequence in create.ts and finish-checkout.ts with calls to this new
persistWorkspace helper.
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts (2)

183-212: ロールバック処理を async/await に統一することを推奨

ロールバックの2箇所で ctx.git(worktreePath).then((g) => g.raw(...)).catch(...) という Promise チェーンを使っていますが、関数自体は async なのでコーディングガイドライン通り try/catch + await に統一するほうが読みやすく、スタックトレースも改善されます。

♻️ Proposed refactor
 	} catch (err) {
 		// Cloud update failed — roll back git rename to keep git and cloud in sync.
 		if (gitRenamed) {
-			await ctx
-				.git(worktreePath)
-				.then((g) => g.raw(["branch", "-m", deduped, oldBranchName]))
-				.catch((rollbackErr) => {
-					console.warn(
-						`[applyAiWorkspaceRename] git branch rollback failed (workspace ${workspaceId}, ${deduped} → ${oldBranchName})`,
-						rollbackErr,
-					);
-				});
+			try {
+				const g = await ctx.git(worktreePath);
+				await g.raw(["branch", "-m", deduped, oldBranchName]);
+			} catch (rollbackErr) {
+				console.warn(
+					`[applyAiWorkspaceRename] git branch rollback failed (workspace ${workspaceId}, ${deduped} → ${oldBranchName})`,
+					rollbackErr,
+				);
+			}
 		}
 		throw err;
 	}
 
 	// No-op detection: if the cloud row's name still equals oldWorkspaceName,
 	// the expected-name guard fired (user renamed in the meantime). Revert
 	// the git rename and skip local DB update so all three stay in lockstep.
 	if (gitRenamed && cloudResult.branch !== deduped) {
-		await ctx
-			.git(worktreePath)
-			.then((g) => g.raw(["branch", "-m", deduped, oldBranchName]))
-			.catch((rollbackErr) => {
-				console.warn(
-					`[applyAiWorkspaceRename] no-op rollback failed (workspace ${workspaceId}, ${deduped} → ${oldBranchName})`,
-					rollbackErr,
-				);
-			});
+		try {
+			const g = await ctx.git(worktreePath);
+			await g.raw(["branch", "-m", deduped, oldBranchName]);
+		} catch (rollbackErr) {
+			console.warn(
+				`[applyAiWorkspaceRename] no-op rollback failed (workspace ${workspaceId}, ${deduped} → ${oldBranchName})`,
+				rollbackErr,
+			);
+		}
 		return;
 	}

As per coding guidelines: "Use async/await for promises in TypeScript/JavaScript".

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

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`
around lines 183 - 212, Both rollback blocks use Promise chaining
(ctx.git(worktreePath).then(...).catch(...)) inside an async function—replace
each with await inside a try/catch to match async/await style: await
ctx.git(worktreePath) to get g, then await g.raw(["branch", "-m", deduped,
oldBranchName]) inside a try, and on error catch it and call console.warn with
the same message and the caught rollbackErr; keep the original thrown err after
the first rollback and the return after the second. Reference symbols: ctx.git,
worktreePath, gitRenamed, deduped, oldBranchName, cloudResult.branch.

198-212: no-op 検出を cloudResult.branch ではなく cloudResult.name に対しても確認することを検討

現在の no-op 検出は「gitRenamed が true かつ cloudResult.branch !== deduped」で expected-name ガードの発火を推定していますが、これは「ガード発火時は branch も含め patch 全体が無視される」という v2Workspace 側の実装に暗黙依存しています。将来 updateNameFromHost の挙動が細粒度化(name だけガード、branch は更新)された場合、このチェックは静かに破綻します。

expectedCurrentName を設定した場合に限り、cloudResult.name === oldWorkspaceName もしくは明示的な no-op フラグを確認する方が契約に対して頑健です。少なくともコメントで「cloudResult.branch チェックは updateNameFromHost のオールオアナッシング契約に依存」という不変条件を明記しておくと安全です。

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

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`
around lines 198 - 212, The no-op detection in applyAiWorkspaceRename currently
relies on gitRenamed && cloudResult.branch !== deduped and implicitly assumes
updateNameFromHost updates branch and name together; change the check to also
consider cloudResult.name when expectedCurrentName is provided (i.e., treat it
as a no-op if expectedCurrentName is set and cloudResult.name ===
oldWorkspaceName), or alternatively detect an explicit no-op flag from the
cloudResult; if you choose not to change logic, at minimum add a clear comment
near gitRenamed/cloudResult.branch explaining that this check depends on
updateNameFromHost being all-or-nothing for branch+name. Ensure you reference
gitRenamed, cloudResult.branch, cloudResult.name, deduped, oldWorkspaceName,
expectedCurrentName, and updateNameFromHost in the change so future
readers/maintainers see the contract.
packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts (1)

15-47: 進行状態 creating_worktree を冪等性チェック前に設定している点(軽微)

Line 22 で creating_worktree を設定した直後、Line 31–38 で既存ワークスペースを検出し Line 40 で clearProgress しています。レンダラが creating_worktree 状態を一瞬観測して即座にクリアされる挙動になるため、進行状態を購読する UI 側でフラッシュが出る可能性があります。

冪等ケースでは setProgress(input.pendingId, "creating_worktree") を既存チェックの後ろに移すか、冪等ヒット時のみ専用状態(例: "reusing_existing")を通すと UX が滑らかになります。優先度は低いです。

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

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts`
around lines 15 - 47, 現在は setProgress(input.pendingId, "creating_worktree")
が既存ワークスペースの冪等性チェックより前に呼ばれており、UI に短いフラッシュを生じさせるため、PR
パスの処理(derivePrLocalBranchName →
ctx.db.query.workspaces.findFirst)で既存ワークスペースを確認するロジックの前に setProgress
を移動するか、冪等ヒット時のみ別の状態を使うように修正してください(対象箇所: protectedProcedure checkout, 関数/識別子:
setProgress, clearProgress, derivePrLocalBranchName,
ctx.db.query.workspaces.findFirst, alreadyExists return
path)。具体的には既存チェックを先に行い、既存があれば必要に応じて setProgress(input.pendingId,
"reusing_existing") を設定してから clearProgress を呼び出して返却し、既存でなければその後に
setProgress(input.pendingId, "creating_worktree") を呼ぶようにしてください。
packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts (1)

106-152: ステール行削除と新規 insert がトランザクション外で実行される点

Line 118 の delete と Line 122–134 の insert がトランザクションにラップされていないため、停電やプロセスクラッシュが両者の間に入った場合、ローカル DB に (projectId, branch) のマッピングが一時的に消える隙間が生じます。さらに insert 失敗時のロールバック(Line 140)はクラウド側を消すだけで、先に消したステールローカル行は復元しません。

実害は限定的(リトライで再 adopt 可能)ですが、Drizzle の同期 API でも ctx.db.transaction((tx) => { ... }) でまとめれば一貫性が保てます。優先度は低いので後続 PR でも問題ありません。

♻️ Proposed fix
-		const stale = ctx.db.query.workspaces
-			.findFirst({
-				where: and(
-					eq(workspaces.projectId, input.projectId),
-					eq(workspaces.branch, branch),
-				),
-			})
-			.sync();
-		if (stale && stale.id !== cloudRow.id) {
-			ctx.db.delete(workspaces).where(eq(workspaces.id, stale.id)).run();
-		}
-
-		try {
-			ctx.db
-				.insert(workspaces)
-				.values({ id: cloudRow.id, projectId: input.projectId, worktreePath, branch })
-				.onConflictDoUpdate({
-					target: workspaces.id,
-					set: { projectId: input.projectId, worktreePath, branch },
-				})
-				.run();
-		} catch (err) {
+		try {
+			ctx.db.transaction((tx) => {
+				const stale = tx.query.workspaces
+					.findFirst({
+						where: and(
+							eq(workspaces.projectId, input.projectId),
+							eq(workspaces.branch, branch),
+						),
+					})
+					.sync();
+				if (stale && stale.id !== cloudRow.id) {
+					tx.delete(workspaces).where(eq(workspaces.id, stale.id)).run();
+				}
+				tx.insert(workspaces)
+					.values({ id: cloudRow.id, projectId: input.projectId, worktreePath, branch })
+					.onConflictDoUpdate({
+						target: workspaces.id,
+						set: { projectId: input.projectId, worktreePath, branch },
+					})
+					.run();
+			});
+		} catch (err) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts`
around lines 106 - 152, The stale-row delete
(ctx.db.delete(workspaces).where(...)) and the insert
(ctx.db.insert(workspaces).values(...).onConflictDoUpdate(...)) must be executed
inside a single transaction to avoid a window where the (projectId, branch)
mapping is missing; wrap both operations in ctx.db.transaction(...) and use the
transaction handle (tx.query.workspaces.findFirst, tx.delete(workspaces).where,
tx.insert(workspaces)...run) so both the stale removal and upsert are atomic and
will roll back together on error, and then remove or adjust the post-error cloud
cleanup logic that currently calls ctx.api.v2Workspace.delete on insert failure
(since the transaction will preserve/restore local state).
packages/host-service/src/trpc/router/workspace-creation/procedures/search-branches.ts (3)

17-22: BranchAccumtype エイリアスでなく interface で定義を。

純粋なオブジェクト形状のため、リポジトリのコーディング規約に沿って interface 宣言が望ましいです。

🛠 Proposed fix
-type BranchAccum = {
-	name: string;
-	lastCommitDate: number;
-	isLocal: boolean;
-	isRemote: boolean;
-};
+interface BranchAccum {
+	name: string;
+	lastCommitDate: number;
+	isLocal: boolean;
+	isRemote: boolean;
+}

As per coding guidelines: "Prefer interface for defining object shapes in TypeScript".

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

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/search-branches.ts`
around lines 17 - 22, Replace the type alias BranchAccum with an equivalent
interface declaration (interface BranchAccum { name: string; lastCommitDate:
number; isLocal: boolean; isRemote: boolean; }) to follow the repo's coding
guideline preferring interfaces for plain object shapes; update any imports/uses
that reference BranchAccum if needed but keep the property names and types
unchanged.

43-50: git.fetch にタイムアウトやキャンセルを検討。

ユーザー側のネットワーク断・プロキシ経路のハングでリモートフェッチが長時間ブロックすると、このクエリ自体がリクエスト・スレッドを掴んで応答が返らなくなります。--quiet だけでは時間上限はつかないので、AbortSignal 対応または外側でのタイムアウトラッパーを入れておくと体感レイテンシが安定します。Chill モードなので任意対応で構いません。

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

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/search-branches.ts`
around lines 43 - 50, The remote git.fetch call can hang indefinitely; wrap the
fetch so it respects a timeout/abort: when input.refresh and
shouldRefetchRemote(input.projectId) is true, create an AbortController (or
equivalent timeout wrapper) tied to a short configurable timeout, pass its
signal into git.fetch (or use Promise.race to reject after timeout if git.fetch
doesn't accept signals), ensure you call markRefetchRemote(input.projectId)
before attempting fetch, clear/abort the controller on success, and catch both
fetch errors and abort/timeouts to proceed with cached refs as currently done;
reference git.fetch, shouldRefetchRemote, markRefetchRemote, input.refresh, and
input.projectId when making the change.

63-71: workspaces クエリはカラムを射影したほうが経済的です。

.select() は全カラムを取得しますが、後続で branch しか利用していません。Drizzle の投影を使えば転送量とマッピングコストを抑えられます。1 プロジェクトあたりの workspace 行数は通常少ないので optional ですが、読みやすさの面でも明示的な方が良いです。

🛠 Proposed fix
 		const workspaceBranches = new Set<string>(
 			ctx.db
-				.select()
+				.select({ branch: workspaces.branch })
 				.from(workspaces)
 				.where(eq(workspaces.projectId, input.projectId))
 				.all()
 				.map((workspace) => workspace.branch)
 				.filter((branch): branch is string => Boolean(branch)),
 		);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/search-branches.ts`
around lines 63 - 71, The query currently calls ctx.db.select() which fetches
all columns but only uses the branch field; change the Drizzle projection to
select only workspaces.branch (e.g., use ctx.db.select(workspaces.branch) or
select({branch: workspaces.branch}) from workspaces) in the block that
constructs workspaceBranches so you only retrieve and map the branch values;
also update the subsequent mapping/filtering to match the projected result shape
(string or {branch: string}) so workspaceBranches remains a Set<string>.
packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts (1)

94-121: findWorktreeAtPath でユーザー指定パスに symlink が含まれる場合、パス比較が失敗する可能性あり

resolvePathnode:path.resolve() の別名で、相対パスと .. の正規化のみ行い symlink を解決しません。adopt で v1→v2 移行時にユーザーが明示的に worktreePath を指定した場合、そのパスに symlink が含まれていると、git worktree list --porcelain が返す正準パス(macOS での /var/private/var など)との比較に失敗し、adopt が失敗する可能性があります。

ただし、本フォークで Superset が管理するworktree は <repoPath>/.worktrees/<branch> 配下に統一されており(コメント"Worktrees are under /.worktrees/, not ~/.superset/worktrees"参照)、この場合は listWorktreeBranches が返す正規化済みパスが使用されるため、実際のリスクは adopt での ユーザー指定パス処理に限定されます。将来的には fs.promises.realpath の併用を検討する価値があります。

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

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts`
around lines 94 - 121, findWorktreeAtPath currently uses resolvePath
(path.resolve) which does not resolve symlinks, causing mismatches with git
worktree's canonical paths; change the comparison to use fs.promises.realpath
for both the input worktreePath and the currentPath parsed from git output
(within findWorktreeAtPath) so symlinks are resolved before comparing to
targetPath, and fall back to resolvePath if realpath fails (catch and degrade
gracefully); reference resolvePath and the findWorktreeAtPath function and
ensure you await realpath for both sides of the equality check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts`:
- Around line 165-182: checkout path is missing the remote-tracking-only guard
that create.ts uses: ensure finishCheckout does not unconditionally write
branch.<branch>.base for local-only names. Reconstruct the startPoint from the
provided baseBranchSource (like create.ts) or add the same check
(startPoint.kind === "remote-tracking") inside finishCheckout before calling
git.raw(["config", `branch.${branchName}.base`, ...]). Also update the
misleading comment "matches create's head-start-point behavior" to state that
create applies a kind filter so finishCheckout must mirror that behavior
(remote-tracking-only) rather than only checking presence of args.baseBranch.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts`:
- Around line 140-150: The regex used to parse reflog lines is greedy and thus
miscaptures branch names that themselves contain " to "; update the pattern used
in branch-search (variable re) to make the first .+ non-greedy (e.g. change
/^checkout: moving from .+ to (.+)$/ to /^checkout: moving from .*? to (.+)$/ or
equivalent), then keep the existing logic that trims name and skips SHA-like
values so branch names with " to " are captured correctly into order.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts`:
- Around line 34-51: The finishCheckout call is being passed
input.composer.baseBranch unconditionally which allows local-only branches to be
written into branch.<name>.base and later referenced as
origin/<local-branch-name> by resolveBaseComparison; update both checkout.ts
call sites (the paths that currently forward input.composer.baseBranch) to only
pass baseBranch when the start point is remote-tracking (i.e. startPoint.kind
=== "remote-tracking") or otherwise validate using the same baseBranchSource
logic used in create.ts, so finishCheckout only receives a remote-tracking
baseBranch and local-only branches are never recorded.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/progress-store.ts`:
- Around line 41-42: getProgress currently lets in-progress entries expire
because updatedAt is only set by setProgress, so long-running steps
(runSetupScript, heavy checkout) get swept as stale and return null; fix by
either refreshing the TTL on poll — e.g., when getProgress(pendingId) finds a
non-null progress, update its updatedAt (or call setProgress/refreshProgress) to
extend life — or change sweepStaleProgress to only remove entries whose status
is terminal (completed/failed) and rely on explicit cleanup on completion; apply
the same change to the related logic that uses sweepStaleProgress (lines around
the other get/cleanup calls referenced in the diff).

In `@packages/host-service/src/trpc/router/workspace-creation/shared/types.ts`:
- Line 29: The CheckoutResult discriminated-union is incorrect: currently it
defines alreadyExists?: false but callers return alreadyExists: true or omit the
field for new checkouts; update the CheckoutResult type (in
packages/host-service/src/trpc/router/workspace-creation/shared/types.ts) to
reflect both cases either by using a proper union of the two shapes (one variant
with alreadyExists: true and one without the field) or by making alreadyExists?:
true (optional true) so that result.alreadyExists === true can be used for
type-safe branching; adjust the exported CheckoutResult type definition
accordingly.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts`:
- Around line 13-18: The current check lets branchName "" or "." pass because
resolve(repoPath, ".worktrees", branchName) equals worktreesRoot; update the
validation around worktreesRoot/worktreePath to explicitly reject empty and
special-segment names by validating branchName first (e.g., if branchName === ""
|| branchName === "." || branchName === ".." then throw), then keep the existing
check that worktreePath is either equal to or a child of worktreesRoot (using
worktreesRoot and worktreePath) to prevent path traversal; reference branchName,
worktreesRoot, and worktreePath to locate and harden the guard before subsequent
create/delete logic.

In `@packages/trpc/src/router/v2-workspace/v2-workspace.ts`:
- Around line 225-249: The current SELECT-then-UPDATE flow for handling
input.expectedCurrentName is vulnerable to a TOCTOU race because the name check
happens in JS instead of being enforced in the UPDATE WHERE clause; change the
dbWs.update(...) call to include the name equality guard when
input.expectedCurrentName is provided (i.e. add eq(v2Workspaces.name,
input.expectedCurrentName) into the where chain alongside eq(v2Workspaces.id,
workspace.id)), and then treat a 0-row update as a no-op by re-querying the
workspace by id (using dbWs or the existing read path) and returning that
current row (if still present) or throwing the existing NOT_FOUND error only if
the workspace truly no longer exists.

---

Nitpick comments:
In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts`:
- Around line 106-152: The stale-row delete
(ctx.db.delete(workspaces).where(...)) and the insert
(ctx.db.insert(workspaces).values(...).onConflictDoUpdate(...)) must be executed
inside a single transaction to avoid a window where the (projectId, branch)
mapping is missing; wrap both operations in ctx.db.transaction(...) and use the
transaction handle (tx.query.workspaces.findFirst, tx.delete(workspaces).where,
tx.insert(workspaces)...run) so both the stale removal and upsert are atomic and
will roll back together on error, and then remove or adjust the post-error cloud
cleanup logic that currently calls ctx.api.v2Workspace.delete on insert failure
(since the transaction will preserve/restore local state).

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts`:
- Around line 15-47: 現在は setProgress(input.pendingId, "creating_worktree")
が既存ワークスペースの冪等性チェックより前に呼ばれており、UI に短いフラッシュを生じさせるため、PR
パスの処理(derivePrLocalBranchName →
ctx.db.query.workspaces.findFirst)で既存ワークスペースを確認するロジックの前に setProgress
を移動するか、冪等ヒット時のみ別の状態を使うように修正してください(対象箇所: protectedProcedure checkout, 関数/識別子:
setProgress, clearProgress, derivePrLocalBranchName,
ctx.db.query.workspaces.findFirst, alreadyExists return
path)。具体的には既存チェックを先に行い、既存があれば必要に応じて setProgress(input.pendingId,
"reusing_existing") を設定してから clearProgress を呼び出して返却し、既存でなければその後に
setProgress(input.pendingId, "creating_worktree") を呼ぶようにしてください。

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts`:
- Around line 214-230: Replace the mixed await/.catch pattern around
ctx.api.v2Workspace.create.mutate with a try/catch block to match the
surrounding code style: move the awaited call to inside try, and in the catch
call clearProgress(input.pendingId), await rollbackWorktree(), log the error
(same message using console.error with the err and context like
"[workspaceCreation.create] v2Workspace.create failed"), then rethrow the error
so callers still see it; reference the cloudRow assignment,
ctx.api.v2Workspace.create.mutate, clearProgress, rollbackWorktree, and
input.pendingId when making the change.
- Around line 197-270: The host registration -> cloud workspace create -> local
persist -> per-step rollback sequence is duplicated between create.ts and
finish-checkout.ts; extract that flow into a shared helper (e.g.,
persistWorkspace) and call it from both places so rollback logic is centralized.
Specifically, encapsulate the calls to ctx.api.device.ensureV2Host.mutate
(ensureV2Host), ctx.api.v2Workspace.create.mutate (v2Workspace.create) and the
local DB insert (ctx.db.insert(workspaces).values(...).run()) into the helper,
accept inputs for cloud/local payloads and a rollback handler list, perform
try/catch around each step inside the helper, run registered rollback actions on
failure, and surface a uniform error (TRPCError) back to callers; then replace
the inline sequence in create.ts and finish-checkout.ts with calls to this new
persistWorkspace helper.

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/search-branches.ts`:
- Around line 17-22: Replace the type alias BranchAccum with an equivalent
interface declaration (interface BranchAccum { name: string; lastCommitDate:
number; isLocal: boolean; isRemote: boolean; }) to follow the repo's coding
guideline preferring interfaces for plain object shapes; update any imports/uses
that reference BranchAccum if needed but keep the property names and types
unchanged.
- Around line 43-50: The remote git.fetch call can hang indefinitely; wrap the
fetch so it respects a timeout/abort: when input.refresh and
shouldRefetchRemote(input.projectId) is true, create an AbortController (or
equivalent timeout wrapper) tied to a short configurable timeout, pass its
signal into git.fetch (or use Promise.race to reject after timeout if git.fetch
doesn't accept signals), ensure you call markRefetchRemote(input.projectId)
before attempting fetch, clear/abort the controller on success, and catch both
fetch errors and abort/timeouts to proceed with cached refs as currently done;
reference git.fetch, shouldRefetchRemote, markRefetchRemote, input.refresh, and
input.projectId when making the change.
- Around line 63-71: The query currently calls ctx.db.select() which fetches all
columns but only uses the branch field; change the Drizzle projection to select
only workspaces.branch (e.g., use ctx.db.select(workspaces.branch) or
select({branch: workspaces.branch}) from workspaces) in the block that
constructs workspaceBranches so you only retrieve and map the branch values;
also update the subsequent mapping/filtering to match the projected result shape
(string or {branch: string}) so workspaceBranches remains a Set<string>.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts`:
- Around line 94-121: findWorktreeAtPath currently uses resolvePath
(path.resolve) which does not resolve symlinks, causing mismatches with git
worktree's canonical paths; change the comparison to use fs.promises.realpath
for both the input worktreePath and the currentPath parsed from git output
(within findWorktreeAtPath) so symlinks are resolved before comparing to
targetPath, and fall back to resolvePath if realpath fails (catch and degrade
gracefully); reference resolvePath and the findWorktreeAtPath function and
ensure you await realpath for both sides of the equality check.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts`:
- Around line 111-140: The catch block for the local DB insert can leave a
zombie cloud workspace if ctx.api.v2Workspace.delete.mutate fails; capture the
cleanup error (cleanupErr) when delete.catch runs, log it with context, and
include that cleanupErr information in the TRPCError you throw (e.g., append to
the message or set as the error cause/metadata) so callers are aware a cloud
rollback also failed; also consider enqueuing the failed cloud cleanup for retry
if you have a retry/reconciliation mechanism (pass cloudRow.id and cleanupErr to
that queue) and ensure rollbackWorktree and clearProgress are still executed.

In `@packages/host-service/src/trpc/router/workspace-creation/shared/types.ts`:
- Around line 5-30: The exported object-shaped types TerminalDescriptor,
BranchRow, and CheckoutResult should be converted from type aliases to
interfaces to follow the project's TS convention; update declarations for
TerminalDescriptor, BranchRow, and CheckoutResult to use interface instead of
type (preserving all properties, optional flags like alreadyExists?: false, and
array/primitive types) while leaving any non-object derived types (e.g.,
GitClient-related Awaited/ReturnType usage) unchanged.

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`:
- Around line 183-212: Both rollback blocks use Promise chaining
(ctx.git(worktreePath).then(...).catch(...)) inside an async function—replace
each with await inside a try/catch to match async/await style: await
ctx.git(worktreePath) to get g, then await g.raw(["branch", "-m", deduped,
oldBranchName]) inside a try, and on error catch it and call console.warn with
the same message and the caught rollbackErr; keep the original thrown err after
the first rollback and the return after the second. Reference symbols: ctx.git,
worktreePath, gitRenamed, deduped, oldBranchName, cloudResult.branch.
- Around line 198-212: The no-op detection in applyAiWorkspaceRename currently
relies on gitRenamed && cloudResult.branch !== deduped and implicitly assumes
updateNameFromHost updates branch and name together; change the check to also
consider cloudResult.name when expectedCurrentName is provided (i.e., treat it
as a no-op if expectedCurrentName is set and cloudResult.name ===
oldWorkspaceName), or alternatively detect an explicit no-op flag from the
cloudResult; if you choose not to change logic, at minimum add a clear comment
near gitRenamed/cloudResult.branch explaining that this check depends on
updateNameFromHost being all-or-nothing for branch+name. Ensure you reference
gitRenamed, cloudResult.branch, cloudResult.name, deduped, oldWorkspaceName,
expectedCurrentName, and updateNameFromHost in the change so future
readers/maintainers see the contract.

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/list-branch-names.ts`:
- Around line 33-35: The catch block in list-branch-names.ts is silently
swallowing errors (it currently reads "} catch { return []; }"); update the
catch to capture the thrown error (e.g., "catch (err)") and add a single-line
console.warn or logger call that includes contextual text and the error object
(for example: "console.warn('listBranchNames: failed to list branches', err)"),
then still return [] so the fallback behavior remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c06ddfe-ab02-456b-8cd4-1d69833b8383

📥 Commits

Reviewing files that changed from the base of the PR and between 0ad4dce and 6eb0a81.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • packages/host-service/package.json
  • packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/generate-branch-name.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/get-context.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/get-github-issue-content.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/get-github-pull-request-content.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/get-progress.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/index.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/search-branches.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/search-github-issues.ts
  • packages/host-service/src/trpc/router/workspace-creation/procedures/search-pull-requests.ts
  • packages/host-service/src/trpc/router/workspace-creation/schemas.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/git-config.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/local-project.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/progress-store.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/project-helpers.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/setup-terminal.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/start-point.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/types.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts
  • packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
  • packages/host-service/src/trpc/router/workspace-creation/utils/list-branch-names.ts
  • packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
  • packages/trpc/src/router/v2-workspace/v2-workspace.ts

Comment on lines +165 to +182
// FORK NOTE: only write for remote-tracking start points. Downstream
// (resolveBaseComparison) always rebuilds the compare ref as
// `origin/${baseBranch}`, so a local-only branch name would resolve
// to a non-existent `origin/<local-name>` and the Changes tab would
// silently break. Skipping the write leaves baseBranch null for
// local-only bases — downstream falls back to the default branch.
// Upstream split uses `startPoint.kind !== "head"` which would write
// for local branches too — we intentionally diverge here.
if (startPoint.kind === "remote-tracking") {
await git
.raw(["config", `branch.${branchName}.base`, startPoint.shortName])
.catch((err) => {
console.warn(
`[workspaceCreation.create] failed to record base branch ${startPoint.shortName}:`,
err,
);
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify finish-checkout.ts applies the same "remote-tracking only" filter as create.ts
fd finish-checkout.ts --exec cat {}

Repository: MocA-Love/superset

Length of output: 4335


🏁 Script executed:

# Find and examine checkout.ts to see how baseBranch flows from composer to finishCheckout
fd -e ts -e tsx | xargs rg -l "checkout" | head -20

Repository: MocA-Love/superset

Length of output: 1376


🏁 Script executed:

# Search for where composer.baseBranch is used in checkout path
rg "composer\.baseBranch|\.baseBranch" --type ts --max-count=10 -B 2 -A 2

Repository: MocA-Love/superset

Length of output: 23225


🏁 Script executed:

# Check if there's any validation on baseBranch in the checkout flow
rg "baseBranch.*kind|startPoint.*kind" --type ts -B 3 -A 3

Repository: MocA-Love/superset

Length of output: 4520


checkout パスでもリモート追跡ブランチのみフィルタを適用する必要あり

create.tsstartPoint.kind === "remote-tracking" でフィルタしてから branch.<name>.base を書き込みます(165-182行)。一方、checkout.tsinput.composer.baseBranch(プレーン文字列)を finishCheckout にそのまま渡し、finishCheckout はそれを無条件に書き込みます(if (args.baseBranch) のみ)。

checkout 経路で composer.baseBranch に local-only ブランチが渡される場合、同じ fork フィルタが欠落しているため、Changes タブが origin/<local-name> で壊れる既知症状が再発します。checkout でも create のように baseBranchSource を受け取り、startPoint を再構成してから kind フィルタを適用するか、あるいは finishCheckout に同じリモート追跡フィルタを追加してください。

finishCheckout 内のコメント「matches create's head-start-point behavior」は誤解を招きます。create は kind フィルタも適用しており、単なる存在チェック(if (args.baseBranch))との一致ではありません。

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

In
`@packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts`
around lines 165 - 182, checkout path is missing the remote-tracking-only guard
that create.ts uses: ensure finishCheckout does not unconditionally write
branch.<branch>.base for local-only names. Reconstruct the startPoint from the
provided baseBranchSource (like create.ts) or add the same check
(startPoint.kind === "remote-tracking") inside finishCheckout before calling
git.raw(["config", `branch.${branchName}.base`, ...]). Also update the
misleading comment "matches create's head-start-point behavior" to state that
create applies a kind filter so finishCheckout must mirror that behavior
(remote-tracking-only) rather than only checking presence of args.baseBranch.

Comment on lines +140 to +150
const re = /^checkout: moving from .+ to (.+)$/;
for (const line of raw.split("\n")) {
const match = re.exec(line);
if (!match?.[1]) continue;
const name = match[1].trim();
if (!name || /^[0-9a-f]{7,40}$/.test(name)) continue;
if (!order.has(name)) {
order.set(name, order.size);
if (order.size >= limit) break;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

reflog パース時の貪欲マッチで、ブランチ名に to を含む場合に誤パースが発生します。

/^checkout: moving from .+ to (.+)$/.+ は貪欲かつ to 前後がリテラル空白のため、移動先ブランチ名に to が含まれると最後の to でスプリットされ、キャプチャがブランチ名の末尾断片のみになります。例: checkout: moving from main to feat/add to cartcart を捕捉し、本来の feat/add to cart は recencyMap に載らず、該当ブランチの並びが正しくなくなります。ソート品質のみの影響なので minor ですが、非貪欲化で確実に修正できます。

🛠 Proposed fix
-		const re = /^checkout: moving from .+ to (.+)$/;
+		const re = /^checkout: moving from .+? to (.+)$/;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts`
around lines 140 - 150, The regex used to parse reflog lines is greedy and thus
miscaptures branch names that themselves contain " to "; update the pattern used
in branch-search (variable re) to make the first .+ non-greedy (e.g. change
/^checkout: moving from .+ to (.+)$/ to /^checkout: moving from .*? to (.+)$/ or
equivalent), then keep the existing logic that trims name and skips SHA-like
values so branch names with " to " are captured correctly into order.

Comment on lines +34 to +51
// Record the base branch for the Changes tab (skipped if unset — matches
// `create`'s head-start-point behavior).
if (args.baseBranch) {
await args.git
.raw([
"-C",
args.worktreePath,
"config",
`branch.${args.branch}.base`,
args.baseBranch,
])
.catch((err) => {
console.warn(
`[workspaceCreation.checkout] failed to record base branch ${args.baseBranch}:`,
err,
);
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# finishCheckout への baseBranch 引数の組み立て箇所を列挙し、
# ローカル専用判定(リモート追跡の存在確認など)が残っているかを確認する。
rg -nP --type=ts -C6 '\bfinishCheckout\s*\('

# checkout/adopt 手続きで baseBranch を決定している箇所の周辺
fd -e ts . 'packages/host-service/src/trpc/router/workspace-creation/procedures' \
  --exec rg -nP -C3 '\bbaseBranch\b' {}

Repository: MocA-Love/superset

Length of output: 6448


🏁 Script executed:

# View checkout.ts to see if baseBranch filtering occurs before finishCheckout calls
head -n 300 packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts | tail -n 150

Repository: MocA-Love/superset

Length of output: 4616


🏁 Script executed:

# Look for the complete context around the startPoint.kind check
rg -nP -B5 -A5 'startPoint\.kind' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# Check if baseBranch is filtered based on remote-tracking logic
rg -nP -B3 -A3 'remote-tracking|refs/remotes' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts

Repository: MocA-Love/superset

Length of output: 463


🏁 Script executed:

# Check adopt.ts for similar finishCheckout calls
rg -nP -B5 -A10 'finishCheckout' packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# Find where composer.baseBranch is set or validated
rg -nP 'composer\.baseBranch|baseBranch.*=' packages/host-service/src/trpc/router/workspace-creation/ -B2 -A2

Repository: MocA-Love/superset

Length of output: 5140


🏁 Script executed:

# Look for any filtering logic around baseBranch in the broader workspace-creation context
rg -nP 'local.*only|remote.*tracking|isRemoteTracking' packages/host-service/src/trpc/router/workspace-creation/ -B3 -A3

Repository: MocA-Love/superset

Length of output: 21714


🏁 Script executed:

# Check if there's filtering in the input preparation/normalization
fd -e ts packages/host-service/src/trpc/router/workspace-creation/ -exec rg -l 'baseBranch' {} \; | head -20

Repository: MocA-Love/superset

Length of output: 306


🏁 Script executed:

# View the complete finish-checkout.ts implementation to see if filtering happens there
cat -n packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts | head -100

Repository: MocA-Love/superset

Length of output: 3593


🏁 Script executed:

# Check if checkout.ts uses baseBranchSource to filter baseBranch
rg -nP 'baseBranchSource' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts -B3 -A3

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# See the complete section in create.ts showing the filtering
sed -n '162,180p' packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts

Repository: MocA-Love/superset

Length of output: 981


🏁 Script executed:

# Check what composer object contains and where it's prepared
rg -nP 'composer\s*[:=]' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts | head -20

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# Check the full checkout.ts around the finishCheckout calls to see entire flow
sed -n '140,160p' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts

Repository: MocA-Love/superset

Length of output: 668


🏁 Script executed:

# Check what input.composer contains in checkout schema/type
rg -nP 'input\.composer' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts -B2 -A2 | head -40

Repository: MocA-Love/superset

Length of output: 433


🏁 Script executed:

# Verify the final piece: does checkout input schema enforce baseBranch filtering?
sed -n '1,50p' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts

Repository: MocA-Love/superset

Length of output: 1889


🏁 Script executed:

# Check if there's any validation/filtering of baseBranch in checkoutInputSchema
cat -n packages/host-service/src/trpc/router/workspace-creation/schemas.ts | grep -A 20 "checkoutInputSchema"

Repository: MocA-Love/superset

Length of output: 1002


🏁 Script executed:

# See if composer is built/filtered anywhere before being used in checkout
rg -nP 'composer\s*=' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts -B5 -A5

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# Final check: confirm create.ts uses startPoint.kind filter but checkout doesn't
diff -u <(sed -n '165,176p' packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts) <(sed -n '34,51p' packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts) || true

Repository: MocA-Love/superset

Length of output: 150


baseBranchの「ローカル専用ブランチ除外」ポリシーがcheckout.tsで実装されていません — 回帰リスク有り。

create.ts(165〜173行目)ではif (startPoint.kind === "remote-tracking")で明示的にフィルタされていますが、checkout.tsの両呼び出し箇所(147〜153行目と251〜257行目)はinput.composer.baseBranchを無条件にfinishCheckoutに渡します。

finishCheckout(34〜51行目)はこの値を無フィルタで書き込むため、ローカル専用ブランチがbranch.<name>.baseに記録される可能性があります。FORK NOTEの通り、下流のresolveBaseComparisonorigin/${baseBranch}として参照するため、存在しないorigin/<local-branch-name>に解決され、Changes タブが無音で破綻します。

checkout.tsの両パスで、create.tsと同様にstartPoint.kind === "remote-tracking"チェックを追加するか、baseBranchSourceを用いた同等の検証が必要です。

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

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts`
around lines 34 - 51, The finishCheckout call is being passed
input.composer.baseBranch unconditionally which allows local-only branches to be
written into branch.<name>.base and later referenced as
origin/<local-branch-name> by resolveBaseComparison; update both checkout.ts
call sites (the paths that currently forward input.composer.baseBranch) to only
pass baseBranch when the start point is remote-tracking (i.e. startPoint.kind
=== "remote-tracking") or otherwise validate using the same baseBranchSource
logic used in create.ts, so finishCheckout only receives a remote-tracking
baseBranch and local-only branches are never recorded.

Comment on lines +41 to +42
export function getProgress(pendingId: string): ProgressStep[] | null {
return createProgress.get(pendingId)?.steps ?? null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

5分固定 TTL だと進行中ジョブの progress が途中で消えます。

updatedAtsetProgress() でしか更新されないので、同じステップが 5 分を超えると次の getProgress 呼び出しで sweepStaleProgress() に消され、まだ処理中でも null が返ります。runSetupScript や重い checkout だと普通に起こりうるので、ポーリング時に延命するか、完了/失敗時の明示的な cleanup に寄せた方がよいです。

差分案
 export function getProgress(pendingId: string): ProgressStep[] | null {
-	return createProgress.get(pendingId)?.steps ?? null;
+	const entry = createProgress.get(pendingId);
+	if (!entry) return null;
+	entry.updatedAt = Date.now();
+	return entry.steps;
 }

Also applies to: 49-53

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

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/progress-store.ts`
around lines 41 - 42, getProgress currently lets in-progress entries expire
because updatedAt is only set by setProgress, so long-running steps
(runSetupScript, heavy checkout) get swept as stale and return null; fix by
either refreshing the TTL on poll — e.g., when getProgress(pendingId) finds a
non-null progress, update its updatedAt (or call setProgress/refreshProgress) to
extend life — or change sweepStaleProgress to only remove entries whose status
is terminal (completed/failed) and rely on explicit cleanup on completion; apply
the same change to the related logic that uses sweepStaleProgress (lines around
the other get/cleanup calls referenced in the diff).

workspace: { id: string };
terminals: TerminalDescriptor[];
warnings: string[];
alreadyExists?: false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# alreadyExists フラグの実際の利用箇所と、true を返す代替結果型を調べる。
rg -nP --type=ts -C3 '\balreadyExists\b'

Repository: MocA-Love/superset

Length of output: 5329


🏁 Script executed:

cat -n packages/host-service/src/trpc/router/workspace-creation/shared/types.ts

Repository: MocA-Love/superset

Length of output: 1060


🏁 Script executed:

# Check for related return types in the same directory
fd -t f '\.(ts|tsx)$' packages/host-service/src/trpc/router/workspace-creation/ | head -20

Repository: MocA-Love/superset

Length of output: 1731


🏁 Script executed:

# Look for how this type is used/exported
rg -nP --type=ts 'export.*type|interface' packages/host-service/src/trpc/router/workspace-creation/shared/types.ts

Repository: MocA-Love/superset

Length of output: 236


🏁 Script executed:

# Search for the complete return type of the checkout procedure
rg -nP --type=ts -A10 'checkout.*=.*procedure|const checkout' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts | head -40

Repository: MocA-Love/superset

Length of output: 468


🏁 Script executed:

# Check the complete return type of the checkout mutation
rg -nP --type=ts -B5 -A20 'export const checkout = protectedProcedure' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts | head -80

Repository: MocA-Love/superset

Length of output: 1153


🏁 Script executed:

# Search for any union types or type combinations with CheckoutResult
rg -nP --type=ts 'CheckoutResult|alreadyExists.*true' packages/host-service/src/trpc/router/workspace-creation/ -A2 -B2

Repository: MocA-Love/superset

Length of output: 2855


🏁 Script executed:

# Check the adopt.ts procedure to see if it has a different return type
cat -n packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts | head -100

Repository: MocA-Love/superset

Length of output: 4070


🏁 Script executed:

# Get more context from checkout.ts - see all return statements
rg -nP --type=ts -B2 -A5 'return {' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts | head -100

Repository: MocA-Love/superset

Length of output: 272


🏁 Script executed:

# Check how alreadyExists is used on the frontend
rg -nP --type=ts -B3 -A3 'alreadyExists.*true|alreadyExists.*===' apps/desktop/src/renderer/ --max-count=20

Repository: MocA-Love/superset

Length of output: 1488


🏁 Script executed:

# Check if there's a union type or overloaded return type
rg -nP --type=ts 'type.*CheckoutResult|CheckoutResult.*=.*\|' packages/host-service/

Repository: MocA-Love/superset

Length of output: 332


🏁 Script executed:

# Check if checkout.ts has explicit return type annotation
rg -nP --type=ts -B10 'export const checkout' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts | head -30

Repository: MocA-Love/superset

Length of output: 675


🏁 Script executed:

# Check all return statements in checkout.ts to understand return type variations
rg -nP --type=ts -B1 'return \{' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts

Repository: MocA-Love/superset

Length of output: 117


🏁 Script executed:

# Check finish-checkout.ts which declares return type as CheckoutResult
cat -n packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts | head -80

Repository: MocA-Love/superset

Length of output: 3029


🏁 Script executed:

# Check the complete checkout.ts to see all return paths
rg -nP --type=ts -B5 -A10 'finishCheckout' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts | head -60

Repository: MocA-Love/superset

Length of output: 1898


🏁 Script executed:

# Search for explicit return type on checkout mutation
ast-grep --pattern 'export const checkout = $$$' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts

Repository: MocA-Love/superset

Length of output: 28288


🏁 Script executed:

# Look at the full finish-checkout return to see all return statements
rg -nP --type=ts 'return \{' packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts -A5

Repository: MocA-Love/superset

Length of output: 126


🏁 Script executed:

# Look at how frontend uses the result.alreadyExists check
rg -nP --type=ts -B5 -A5 'result\.alreadyExists|\.alreadyExists ===' apps/desktop/src/renderer/

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# Search more broadly for how the checkout result is consumed
rg -nP --type=ts 'useCheckoutDashboardWorkspace|checkout\.mutate' apps/desktop/src/renderer/ -A10 | head -80

Repository: MocA-Love/superset

Length of output: 9535


🏁 Script executed:

# Look for conditional logic related to alreadyExists on the client
rg -nP --type=ts 'alreadyExists' apps/desktop/src/renderer/ -B5 -A5

Repository: MocA-Love/superset

Length of output: 4571


🏁 Script executed:

# Check if TypeScript compiles with this type mismatch
cd packages/host-service && npx tsc --noEmit 2>&1 | grep -A2 -B2 'alreadyExists' | head -30

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# Check the actual inferred return type from checkout.ts mutation
rg -nP --type=ts -B2 'alreadyExists: true as const' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts -A3

Repository: MocA-Love/superset

Length of output: 167


🏁 Script executed:

# Check if there are type errors when compiling
cd packages/host-service && npx tsc --noEmit 2>&1 | head -50

Repository: MocA-Love/superset

Length of output: 321


🏁 Script executed:

# Search for how the return type is actually used/consumed in callers
rg -nP --type=ts 'checkout\.mutate' packages/host-service/ -A15 | head -60

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# Look at the actual mutations calling this to see how the result is used
rg -nP --type=ts -B5 'checkout\.mutate\(' apps/desktop/src/renderer/ -A20 | grep -A15 'const result\|checkout\.mutate'

Repository: MocA-Love/superset

Length of output: 2424


🏁 Script executed:

# Check if there's a return type override or if the type annotation exists
rg -nP --type=ts 'checkout.*:.*mutation' packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts

Repository: MocA-Love/superset

Length of output: 44


🏁 Script executed:

# Check procedures/index.ts to see how checkout is exported
cat -n packages/host-service/src/trpc/router/workspace-creation/procedures/index.ts

Repository: MocA-Love/superset

Length of output: 720


🏁 Script executed:

# Check how the result is used after mutation in the pending page
rg -nP --type=tsx 'checkoutWorkspace\(' apps/desktop/src/renderer/ -B3 -A15 | head -60

Repository: MocA-Love/superset

Length of output: 90


🏁 Script executed:

# Look for type assertions or if statements checking alreadyExists
rg -nP --type=ts 'alreadyExists.*true|if.*alreadyExists' apps/desktop/src/renderer/ -B2 -A3

Repository: MocA-Love/superset

Length of output: 2051


CheckoutResult 型の判別共用体パターンが不完全です。

現在の型定義は alreadyExists?: false となっていますが、実装では以下の2つの戻り値パターンがあります:

  1. 既存ワークスペースの場合(checkout.ts:45): alreadyExists: true as const を返す
  2. 新規作成の場合(checkout.ts:147, 251): finishCheckout() を呼び出し、alreadyExists フィールドを含まない結果を返す

型定義を修正し、判別共用体として両パターンを正しく表現してください:

export type CheckoutResult = 
  | {
      workspace: { id: string };
      terminals: TerminalDescriptor[];
      warnings: string[];
      alreadyExists: true;
    }
  | {
      workspace: { id: string };
      terminals: TerminalDescriptor[];
      warnings: string[];
    };

または、より簡潔に:

export type CheckoutResult = {
  workspace: { id: string };
  terminals: TerminalDescriptor[];
  warnings: string[];
  alreadyExists?: true; // 存在する場合のみ true、省略時は新規作成を示す
};

これにより、呼び出し側での型安全な判別(result.alreadyExists === true による分岐)が適切に型チェックされます。

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

In `@packages/host-service/src/trpc/router/workspace-creation/shared/types.ts` at
line 29, The CheckoutResult discriminated-union is incorrect: currently it
defines alreadyExists?: false but callers return alreadyExists: true or omit the
field for new checkouts; update the CheckoutResult type (in
packages/host-service/src/trpc/router/workspace-creation/shared/types.ts) to
reflect both cases either by using a proper union of the two shapes (one variant
with alreadyExists: true and one without the field) or by making alreadyExists?:
true (optional true) so that result.alreadyExists === true can be used for
type-safe branching; adjust the exported CheckoutResult type definition
accordingly.

Comment on lines +13 to +18
const worktreesRoot = resolve(repoPath, ".worktrees");
const worktreePath = resolve(worktreesRoot, branchName);
if (
worktreePath !== worktreesRoot &&
!worktreePath.startsWith(worktreesRoot + sep)
) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

. / 空文字のブランチ名が .worktrees 直下として通ってしまいます。

この判定は「ルート外への脱出」は防げていますが、branchName === """." のとき worktreePath === worktreesRoot になり、そのまま許可されます。結果として、後続の worktree 作成/削除処理がブランチ用サブディレクトリではなく .worktrees ルート自体を対象にできてしまいます。ここは少なくとも "" / "." / ".." を先に弾いた方が安全です。

差分案
 export function safeResolveWorktreePath(
 	repoPath: string,
 	branchName: string,
 ): string {
+	if (!branchName || branchName === "." || branchName === "..") {
+		throw new TRPCError({
+			code: "BAD_REQUEST",
+			message: "Invalid branch name",
+		});
+	}
+
 	const worktreesRoot = resolve(repoPath, ".worktrees");
 	const worktreePath = resolve(worktreesRoot, branchName);
 	if (
 		worktreePath !== worktreesRoot &&
 		!worktreePath.startsWith(worktreesRoot + sep)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts`
around lines 13 - 18, The current check lets branchName "" or "." pass because
resolve(repoPath, ".worktrees", branchName) equals worktreesRoot; update the
validation around worktreesRoot/worktreePath to explicitly reject empty and
special-segment names by validating branchName first (e.g., if branchName === ""
|| branchName === "." || branchName === ".." then throw), then keep the existing
check that worktreePath is either equal to or a child of worktreesRoot (using
worktreesRoot and worktreePath) to prevent path traversal; reference branchName,
worktreesRoot, and worktreePath to locate and harden the guard before subsequent
create/delete logic.

Comment thread packages/trpc/src/router/v2-workspace/v2-workspace.ts
MocA-Love added a commit that referenced this pull request Apr 24, 2026
Codex レビュー指摘の race condition を修正。`updateNameFromHost` は
find → check → update の 3 段階で、find と update の間にユーザーが
rename を挟むと expectedCurrentName チェックをすり抜けて overwrite
されていた。

expectedCurrentName を SQL レベルの WHERE に積んで UPDATE 自体を
条件付きにすることで、名前が競合して変わっていた場合は 0 行マッチ
→ 現在行を返す → 呼び元 (applyAiWorkspaceRename) で branch 不一致を
検出して git rename をロールバック、という既存の no-op パスに正しく
乗る。

- drizzle-orm から `and` を import
- `expectedCurrentName` 指定時のみ WHERE に追加 (optional 動作維持)
- UPDATE 0 行は "row was renamed between find and update" を意味する
  ので NOT_FOUND ではなく現在行を返す (旧挙動の NOT_FOUND は誤り)
… upstream superset-sh#3697)

upstream superset-sh#3697 の split 構造 (24 ファイル) を fork に取り込み、fork
独自拡張を新 split 構造に再配置。workspace-creation.ts を 1758 行から
28 行の facade に縮小。

- schemas.ts: 全 procedure の zod schemas (baseBranchSource,
  workspaceNameWasAutoGenerated, checkoutPrSchema XOR refine を保持)
- shared/: 11 ファイル (types, project-helpers, local-project,
  progress-store, git-config, setup-terminal, worktree-paths,
  start-point, branch-search, finish-checkout, types)
- procedures/: 11 ファイル (adopt, checkout, create, generate-branch-name,
  get-context, get-github-issue-content, get-github-pull-request-content,
  get-progress, search-branches, search-github-issues, search-pull-requests)
- utils/: 追加 (ai-workspace-names.ts, list-branch-names.ts)

1. **baseBranchSource** (picker hint: local/remote-tracking) →
   schemas.ts + procedures/create.ts + procedures/adopt.ts
2. **PR checkout mode** (derivePrLocalBranchName, detached worktree,
   gh pr checkout --force, pre-existing branch/closed/merged warnings) →
   procedures/checkout.ts
3. **FORK NOTE** コメント群 → 関連 procedure に復元
4. **GitHub link command wiring** (gh issue develop, gh pr create) →
   各 procedure
5. **normalize-github-query.ts** (fork-only URL normalization) → 維持
6. **applyAiWorkspaceRename** + updateNameFromHost no-op detection →
   utils/ai-workspace-names.ts で復元 (PR #9 で fork main から欠落して
   いた関数を本 PR で供給)
7. **worktree path 方針**: <repoPath>/.worktrees/<branch> を維持
   (upstream の ~/.superset/worktrees/<projectId>/<branch> は採用しない。
   既存 workspace/file watcher/migration 互換性のため)

packages/trpc/src/router/v2-workspace/v2-workspace.ts に
`updateNameFromHost` JWT procedure を追加。expectedCurrentName WHERE
guard で並行 user rename の race を検出し、no-op 時は既存 row を
返して host-service 側で git branch rollback できるようにする。

- ai-workspace-names.ts: getSmallModel() が unknown を返すため、
  Agent の model parameter に biome-ignore 付きで cast
- v2-workspace.updateNameFromHost: findFirst の columns に branch
  を追加 (no-op 時の caller での branch 比較に必要)

- bun run typecheck: 27/27 successful
- bun run lint: pass (rg 未 install の環境警告除く)
- fork 固有機能: 19 procedure, ansi_up/@vscode/ripgrep/@xyflow/react,
  TERMINAL_OPTIONS, SUPERSET_WORKSPACE_NAME, dmg.size=4g, desktop 1.5.10
  全健在

packages/host-service/package.json に @mastra/core: 1.25.0 を追加
(applyAiWorkspaceRename が Agent を使うため)。packages/chat が既に
同 version を持つため workspace 共有で解決される。

Closes #415
Codex レビュー指摘の race condition を修正。`updateNameFromHost` は
find → check → update の 3 段階で、find と update の間にユーザーが
rename を挟むと expectedCurrentName チェックをすり抜けて overwrite
されていた。

expectedCurrentName を SQL レベルの WHERE に積んで UPDATE 自体を
条件付きにすることで、名前が競合して変わっていた場合は 0 行マッチ
→ 現在行を返す → 呼び元 (applyAiWorkspaceRename) で branch 不一致を
検出して git rename をロールバック、という既存の no-op パスに正しく
乗る。

- drizzle-orm から `and` を import
- `expectedCurrentName` 指定時のみ WHERE に追加 (optional 動作維持)
- UPDATE 0 行は "row was renamed between find and update" を意味する
  ので NOT_FOUND ではなく現在行を返す (旧挙動の NOT_FOUND は誤り)
@MocA-Love MocA-Love force-pushed the upstream/pr12-split-workspace-creation branch from b220e28 to 9abf35f Compare April 24, 2026 14:39
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 24, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

@MocA-Love MocA-Love merged commit 8b4586d into main Apr 24, 2026
12 of 13 checks passed
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.

Adopt upstream #3697 split workspace-creation router (残る唯一の未取り込み upstream commit)

1 participant