Skip to content

[codex] Add configurable worktree locations#4887

Merged
Kitenite merged 11 commits into
mainfrom
restore-worktrees-locatio
May 28, 2026
Merged

[codex] Add configurable worktree locations#4887
Kitenite merged 11 commits into
mainfrom
restore-worktrees-locatio

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented May 23, 2026

Summary

  • add host-service storage, migration, and APIs for host-level and project-level worktree base directories
  • apply the effective worktree directory during v2 workspace creation, including legacy desktop setting seeding
  • add settings UI for user, host, and project worktree locations, with v2 remote path picking support
  • add configurable per-host branch prefixes with shared HostSelect device picker on /settings/git, project, and host settings; render worktree-location row alongside branch prefix in v2 Git settings

Impact

Users can choose where new worktree workspaces are created and customize branch prefixes at the global/user, host, and project levels. v2 workspaces now use host-service settings instead of only the legacy desktop local setting.

Validation

  • bun test apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts
  • bun test packages/host-service/test/integration/workspace-create-delete.integration.test.ts
  • bun test packages/host-service/src/trpc/router/workspaces/workspace-creation/utils/branch-prefix.test.ts
  • bun run --cwd apps/desktop typecheck
  • bun run lint:fix
  • bun run lint

Open in Stage

Summary by cubic

Adds configurable worktree locations and branch prefixes for v2 workspaces, with host defaults and per-project overrides applied during workspace creation. The Git settings page now targets any device with a unified picker and seeds the legacy worktree location.

  • New Features

    • Worktree location: persist host/project settings in host_settings and projects.worktree_base_dir; TRPC settings.worktreeLocation.get/set, project.setWorktreeBaseDir; resolution order is project override → host setting → ~/.superset/worktrees; host is auto-seeded from SUPERSET_LEGACY_WORKTREE_BASE_DIR. V2 UI adds a remote path picker; Host and Project settings include Worktrees; /settings/git supports ?hostId and shows both Worktree Location and Branch Prefix.
    • Branch prefixes: modes none/github/author/custom, with a host-wide default and optional per-project override; TRPC settings.branchPrefix.get/set, project.setBranchPrefix; applied only to new branches (collision-safe) by workspace.create. Single source of truth for BranchPrefixMode in @superset/shared/workspace-launch; shared BranchPrefixControl and HostSelect. Settings search keeps the Git tab visible in v2 and adds a host worktree item.
  • Bug Fixes

    • V2 Git settings: branch-prefix queries are gated on host online status to avoid errors when targeting offline devices.

Written for commit acf777d. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Host-level and per-project worktree location settings with UI to view/change/reset paths
    • Configurable branch prefix settings for new branches (modes: none, github, author, custom)
    • New host picker and consolidated Git & worktrees settings UI
  • Tests

    • Integration tests for workspace creation with configurable worktree roots
    • Unit tests for branch prefix resolution
  • Documentation

    • Added design docs describing v2 branch-prefix behavior and storage

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ecbec921-d1cd-43f6-8972-f6ddf056f5a9

📥 Commits

Reviewing files that changed from the base of the PR and between f3fc59a and acf777d.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/V2GitSettings/V2GitSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/V2GitSettings/V2GitSettings.tsx

📝 Walkthrough

Walkthrough

Adds host- and project-level worktree base directory and branch-prefix configuration: DB migration and Drizzle schema, tRPC routers and hooks, path normalization utilities, branch-prefix resolution and tests, workspace creation integration, renderer UI components (pickers/selectors), settings registry updates, and integration tests.

Changes

Configuration Infrastructure and APIs

Layer / File(s) Summary
DB migration, Drizzle snapshot, schema
packages/host-service/drizzle/0005_host_settings_and_project_overrides.sql, packages/host-service/drizzle/meta/*, packages/host-service/src/db/schema.ts
Adds host_settings table and per-project worktree_base_dir/branch_prefix_* columns; exports hostSettings and typed project columns.
Worktree path utilities
packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts
Adds defaultWorktreesRoot() and normalizeWorktreeBaseDir(), and makes projectWorktreesRoot()/safeResolveWorktreePath() accept configurable base dirs.
Host settings router
packages/host-service/src/trpc/router/settings/worktree-location.ts, packages/host-service/src/trpc/router/settings/index.ts
New worktreeLocationRouter with get/set, seeds from SUPERSET_LEGACY_WORKTREE_BASE_DIR on first access, and exports HostWorktreeLocationSettings; wired into settingsRouter.
Branch-prefix router & project API
packages/host-service/src/trpc/router/settings/branch-prefix.ts, packages/host-service/src/trpc/router/project/project.ts
Adds branchPrefixRouter (get/set/gitInfo) and projectRouter.setWorktreeBaseDir; project list/get now include worktreeBaseDir.
Branch-prefix utilities
packages/host-service/src/trpc/router/workspace-creation/utils/branch-prefix.ts, packages/shared/src/workspace-launch/branch.ts, packages/local-db/src/schema/zod.ts
Resolves Git author/GitHub identity, resolveProjectBranchPrefix() applies host vs project precedence and suppresses collisions; central BRANCH_PREFIX_MODES and BranchPrefixMode exported and re-used.
Workspace creation integration & tests
packages/host-service/src/trpc/router/workspaces/workspaces.ts, packages/host-service/test/integration/workspace-create-delete.integration.test.ts
Workspace creation computes effective worktreeBaseDir (project override → host fallback) and passes it into path resolution; integration tests cover host-configured root, legacy env-var seeding, and project overrides.
Desktop host-service coordinator
apps/desktop/src/main/lib/host-service-coordinator.ts
Forwards SUPERSET_LEGACY_WORKTREE_BASE_DIR into spawned host-service child env.

Renderer UI Components and Settings Pages

Layer / File(s) Summary
Picker, selector, control components
apps/desktop/src/renderer/routes/_authenticated/settings/components/V2WorktreeLocationPicker/*, apps/desktop/src/renderer/routes/_authenticated/settings/components/HostSelect/*, apps/desktop/src/renderer/routes/_authenticated/settings/components/BranchPrefixControl/*
Adds V2WorktreeLocationPicker (local vs remote selection), HostSelect (host dropdown with online/offline states), and BranchPrefixControl (mode select + custom prefix input with sanitization).
User & host settings UI
apps/desktop/src/renderer/routes/_authenticated/settings/git/components/*, apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/*
Replaces inline worktree logic with UserWorktreeLocationSection (V1 local / V2 host variants) and adds WorktreeLocationSection in host settings (ownership-gated editing).
Project settings UI
apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/*
V2ProjectSettings uses shared HostSelect, adds BranchPrefixSection and WorktreeLocationSection for per-project settings and refreshes project state on change.
V2 Git settings page & routing
apps/desktop/src/renderer/routes/_authenticated/settings/git/components/V2GitSettings/*, apps/desktop/src/renderer/routes/_authenticated/settings/git/page.tsx
New V2GitSettings shows branch-prefix control and worktree picker with optional host selection; route extracts optional hostId and conditionally renders V2 or legacy settings.
Settings registry and tests
apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts, .../settings-search.test.ts
Adds HOST_WORKTREE_LOCATION (v2), moves several worktree/branch items from v1 to shared, updates descriptions, and adds a test to validate visibility in v2.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • superset-sh/superset#4665: Modifies V2 project settings to thread host context (hostId/targetHostUrl) that the main PR's worktree UI depends on
  • superset-sh/superset#2373: Adds/extends host-service project/workspace support in host-service tRPC and router surfaces related to project worktree APIs
  • superset-sh/superset#4675: Touches V2 project settings and remote path picking components used by the new worktree-location UI

Poem

🐰 I hopped through schema, code, and trees,

Home paths set by hosts and projects, please—
Prefixes bloom from author or GH name,
Pickers pop up to find the right frame,
A bunny cheers: configs now play the game!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 '[codex] Add configurable worktree locations' accurately and concisely summarizes the primary change—adding the ability to configure where worktree directories are created.
Description check ✅ Passed The description provides a clear Summary, Impact statement, and Validation section listing multiple test commands. However, it does not include the optional sections from the template (Related Issues, Type of Change, Testing details, Screenshots, Additional Notes).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch restore-worktrees-locatio

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 23, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@stage-review
Copy link
Copy Markdown

stage-review Bot commented May 23, 2026

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 23, 2026

Greptile Summary

This PR adds configurable worktree base directories at user, host, and project levels. It introduces a new host_settings SQLite table and a worktree_base_dir column on projects, a new worktreeLocationRouter tRPC endpoint, and corresponding settings UI for v2 cloud users.

  • Database: Adds host_settings (singleton row, id=1) and a worktree_base_dir column to projects; legacy desktop env var is seeded into the new table on first read.
  • Backend: getEffectiveWorktreeBaseDir resolves precedence (project > host > default); normalizeWorktreeBaseDir handles ~-expansion and absolute path validation; workspace creation now threads the effective base dir through safeResolveWorktreePath.
  • Frontend: V2WorktreeLocationPicker is a shared component supporting both local directory picking and remote path browsing; three new setting sections wire into user, host, and project settings pages.

Confidence Score: 3/5

The workspace creation path is correct and well-tested, but two backend issues need attention: a path traversal gap in tilde expansion and a missing server-side ownership check on the host-level settings mutation.

Tilde expansion in normalizeWorktreeBaseDir resolves .. without bounding the result to the home directory — ~/../../etc is accepted and persisted. Combined with no server-side ownership check on settings.worktreeLocation.set, a non-owner member of a shared host could redirect workspace creation to a sensitive path. The migration, workspace creation logic, and integration tests are clean.

packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts (tilde traversal) and packages/host-service/src/trpc/router/settings/worktree-location.ts (missing ownership check).

Security Review

  • Path traversal in tilde expansion (worktree-paths.ts normalizeWorktreeBaseDir): inputs like ~/../../etc/passwd are accepted and stored — normalize(join(homedir(), rest)) resolves .. sequences without bounding the result to the home directory. A host owner (or any authenticated member if the server-side ownership check is absent) can redirect workspace creation to an arbitrary path on the host filesystem.
  • Missing server-side authorization on host settings (worktree-location.ts set): the endpoint is protectedProcedure-only; non-owner members can call it directly to change the host-wide worktree base dir for all users on that host.

Important Files Changed

Filename Overview
packages/host-service/src/trpc/router/settings/worktree-location.ts New settings router for host-level worktree base dir with legacy env var seeding; set has no server-side ownership check.
packages/host-service/src/trpc/router/project/project.ts Adds setWorktreeBaseDir mutation; update runs before existence check and no authorization is enforced.
packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts Adds normalizeWorktreeBaseDir; tilde expansion does not block ~/../../ traversal.
packages/host-service/src/trpc/router/workspaces/workspaces.ts Threads effective worktree base dir through workspace creation correctly.
packages/host-service/test/integration/workspace-create-delete.integration.test.ts Three new integration tests for host config, legacy seeding, and project override; clean and well-structured.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/host-service/src/trpc/router/settings/worktree-location.ts:82-98
**Missing server-side ownership check on host settings mutation**

`worktreeLocationRouter.set` is guarded only by `protectedProcedure` (authentication), not by host ownership. The UI correctly limits the button to owners via `canEdit={isOwner}`, but any authenticated member of the host service can call this endpoint directly and change the host-wide worktree base dir — affecting where new workspaces land for every project on that host. The same gap applies to `project.setWorktreeBaseDir` in `project.ts`.

### Issue 2 of 3
packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts:17-22
**Tilde expansion does not block upward traversal sequences**

`normalize(join(homedir(), rest))` resolves `..` segments in the path, so an input like `~/../../etc` is accepted and stored as `/etc` (or similar). A host owner (or any authenticated member if the server-side ownership check is absent) can redirect workspace creation for all members to an unexpected path on the host filesystem.

```suggestion
	if (trimmed.startsWith("~")) {
		const rest = trimmed.slice(1);
		if (rest === "" || rest.startsWith("/") || rest.startsWith("\\")) {
			const expanded = resolve(join(homedir(), rest));
			if (!expanded.startsWith(homedir() + sep)) {
				throw new TRPCError({
					code: "BAD_REQUEST",
					message: "Worktree location must not traverse above the home directory",
				});
			}
			return expanded;
		}
	}
```

### Issue 3 of 3
packages/host-service/src/trpc/router/project/project.ts:71-97
**Update runs before project existence check**

The `UPDATE` executes unconditionally; if the `projectId` doesn't exist it silently writes zero rows, and only the subsequent `findFirst` discovers the gap and throws `NOT_FOUND`. Swapping the order — guard first, update only when the project is confirmed — is the safer pattern and avoids a wasted write on bad input.

```suggestion
		.mutation(({ ctx, input }) => {
			const worktreeBaseDir = normalizeWorktreeBaseDir(input.path);

			const project = ctx.db.query.projects
				.findFirst({ where: eq(projects.id, input.projectId) })
				.sync();
			if (!project) {
				throw new TRPCError({
					code: "NOT_FOUND",
					message: "Project is not set up on this host",
				});
			}

			ctx.db
				.update(projects)
				.set({ worktreeBaseDir })
				.where(eq(projects.id, input.projectId))
				.run();

			return {
				id: project.id,
				worktreeBaseDir: worktreeBaseDir ?? null,
				effectiveWorktreeBaseDir: getEffectiveWorktreeBaseDir({
					ctx,
					projectWorktreeBaseDir: worktreeBaseDir,
				}),
			};
		}),
```

Reviews (1): Last reviewed commit: "Add configurable worktree locations" | Re-trigger Greptile

Comment on lines +82 to +98
set: protectedProcedure
.input(z.object({ path: z.string().nullable() }))
.mutation(({ ctx, input }) => {
const worktreeBaseDir = normalizeWorktreeBaseDir(input.path);
ctx.db
.insert(hostSettings)
.values({
id: HOST_SETTINGS_ID,
worktreeBaseDir,
})
.onConflictDoUpdate({
target: hostSettings.id,
set: { worktreeBaseDir },
})
.run();
return toOutput(worktreeBaseDir);
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Missing server-side ownership check on host settings mutation

worktreeLocationRouter.set is guarded only by protectedProcedure (authentication), not by host ownership. The UI correctly limits the button to owners via canEdit={isOwner}, but any authenticated member of the host service can call this endpoint directly and change the host-wide worktree base dir — affecting where new workspaces land for every project on that host. The same gap applies to project.setWorktreeBaseDir in project.ts.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/settings/worktree-location.ts
Line: 82-98

Comment:
**Missing server-side ownership check on host settings mutation**

`worktreeLocationRouter.set` is guarded only by `protectedProcedure` (authentication), not by host ownership. The UI correctly limits the button to owners via `canEdit={isOwner}`, but any authenticated member of the host service can call this endpoint directly and change the host-wide worktree base dir — affecting where new workspaces land for every project on that host. The same gap applies to `project.setWorktreeBaseDir` in `project.ts`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +17 to +22
if (trimmed.startsWith("~")) {
const rest = trimmed.slice(1);
if (rest === "" || rest.startsWith("/") || rest.startsWith("\\")) {
return normalize(join(homedir(), rest));
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Tilde expansion does not block upward traversal sequences

normalize(join(homedir(), rest)) resolves .. segments in the path, so an input like ~/../../etc is accepted and stored as /etc (or similar). A host owner (or any authenticated member if the server-side ownership check is absent) can redirect workspace creation for all members to an unexpected path on the host filesystem.

Suggested change
if (trimmed.startsWith("~")) {
const rest = trimmed.slice(1);
if (rest === "" || rest.startsWith("/") || rest.startsWith("\\")) {
return normalize(join(homedir(), rest));
}
}
if (trimmed.startsWith("~")) {
const rest = trimmed.slice(1);
if (rest === "" || rest.startsWith("/") || rest.startsWith("\\")) {
const expanded = resolve(join(homedir(), rest));
if (!expanded.startsWith(homedir() + sep)) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Worktree location must not traverse above the home directory",
});
}
return expanded;
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts
Line: 17-22

Comment:
**Tilde expansion does not block upward traversal sequences**

`normalize(join(homedir(), rest))` resolves `..` segments in the path, so an input like `~/../../etc` is accepted and stored as `/etc` (or similar). A host owner (or any authenticated member if the server-side ownership check is absent) can redirect workspace creation for all members to an unexpected path on the host filesystem.

```suggestion
	if (trimmed.startsWith("~")) {
		const rest = trimmed.slice(1);
		if (rest === "" || rest.startsWith("/") || rest.startsWith("\\")) {
			const expanded = resolve(join(homedir(), rest));
			if (!expanded.startsWith(homedir() + sep)) {
				throw new TRPCError({
					code: "BAD_REQUEST",
					message: "Worktree location must not traverse above the home directory",
				});
			}
			return expanded;
		}
	}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +71 to +97
.mutation(({ ctx, input }) => {
const worktreeBaseDir = normalizeWorktreeBaseDir(input.path);
ctx.db
.update(projects)
.set({ worktreeBaseDir })
.where(eq(projects.id, input.projectId))
.run();

const project = ctx.db.query.projects
.findFirst({ where: eq(projects.id, input.projectId) })
.sync();
if (!project) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Project is not set up on this host",
});
}

return {
id: project.id,
worktreeBaseDir: project.worktreeBaseDir ?? null,
effectiveWorktreeBaseDir: getEffectiveWorktreeBaseDir({
ctx,
projectWorktreeBaseDir: project.worktreeBaseDir,
}),
};
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Update runs before project existence check

The UPDATE executes unconditionally; if the projectId doesn't exist it silently writes zero rows, and only the subsequent findFirst discovers the gap and throws NOT_FOUND. Swapping the order — guard first, update only when the project is confirmed — is the safer pattern and avoids a wasted write on bad input.

Suggested change
.mutation(({ ctx, input }) => {
const worktreeBaseDir = normalizeWorktreeBaseDir(input.path);
ctx.db
.update(projects)
.set({ worktreeBaseDir })
.where(eq(projects.id, input.projectId))
.run();
const project = ctx.db.query.projects
.findFirst({ where: eq(projects.id, input.projectId) })
.sync();
if (!project) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Project is not set up on this host",
});
}
return {
id: project.id,
worktreeBaseDir: project.worktreeBaseDir ?? null,
effectiveWorktreeBaseDir: getEffectiveWorktreeBaseDir({
ctx,
projectWorktreeBaseDir: project.worktreeBaseDir,
}),
};
}),
.mutation(({ ctx, input }) => {
const worktreeBaseDir = normalizeWorktreeBaseDir(input.path);
const project = ctx.db.query.projects
.findFirst({ where: eq(projects.id, input.projectId) })
.sync();
if (!project) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Project is not set up on this host",
});
}
ctx.db
.update(projects)
.set({ worktreeBaseDir })
.where(eq(projects.id, input.projectId))
.run();
return {
id: project.id,
worktreeBaseDir: worktreeBaseDir ?? null,
effectiveWorktreeBaseDir: getEffectiveWorktreeBaseDir({
ctx,
projectWorktreeBaseDir: worktreeBaseDir,
}),
};
}),
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/project/project.ts
Line: 71-97

Comment:
**Update runs before project existence check**

The `UPDATE` executes unconditionally; if the `projectId` doesn't exist it silently writes zero rows, and only the subsequent `findFirst` discovers the gap and throws `NOT_FOUND`. Swapping the order — guard first, update only when the project is confirmed — is the safer pattern and avoids a wasted write on bad input.

```suggestion
		.mutation(({ ctx, input }) => {
			const worktreeBaseDir = normalizeWorktreeBaseDir(input.path);

			const project = ctx.db.query.projects
				.findFirst({ where: eq(projects.id, input.projectId) })
				.sync();
			if (!project) {
				throw new TRPCError({
					code: "NOT_FOUND",
					message: "Project is not set up on this host",
				});
			}

			ctx.db
				.update(projects)
				.set({ worktreeBaseDir })
				.where(eq(projects.id, input.projectId))
				.run();

			return {
				id: project.id,
				worktreeBaseDir: worktreeBaseDir ?? null,
				effectiveWorktreeBaseDir: getEffectiveWorktreeBaseDir({
					ctx,
					projectWorktreeBaseDir: worktreeBaseDir,
				}),
			};
		}),
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/host-service/src/trpc/router/settings/worktree-location.ts`:
- Around line 48-63: The lazy initialization in the settings read path can race:
two requests may both see no row and both execute the insert for hostSettings
with HOST_SETTINGS_ID causing a PK conflict; to fix, make the insertion
idempotent and conflict-safe by changing the logic around ctx.db.select/insert
to either perform an upsert/insert...on conflict do nothing (then re-select) or
wrap the check+insert in a DB transaction/lock so only one insert succeeds; use
the existing symbols hostSettings, HOST_SETTINGS_ID, ctx.db and
getLegacyWorktreeBaseDir() to locate and replace the current select/insert
sequence with an atomic upsert or a retry-after-no-op-insert-then-select flow.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45cc498f-169d-4540-be9d-c4e3e27b2cf2

📥 Commits

Reviewing files that changed from the base of the PR and between 8374a12 and 2dcf005.

📒 Files selected for processing (24)
  • apps/desktop/src/main/lib/host-service-coordinator.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/V2WorktreeLocationPicker/V2WorktreeLocationPicker.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/V2WorktreeLocationPicker/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/components/UserWorktreeLocationSection/UserWorktreeLocationSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/components/UserWorktreeLocationSection/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/HostSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/WorktreeLocationSection/WorktreeLocationSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/WorktreeLocationSection/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/WorktreeLocationSection/WorktreeLocationSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/WorktreeLocationSection/index.ts
  • packages/host-service/drizzle/0005_big_mach_iv.sql
  • packages/host-service/drizzle/meta/0005_snapshot.json
  • packages/host-service/drizzle/meta/_journal.json
  • packages/host-service/src/db/schema.ts
  • packages/host-service/src/trpc/router/project/project.ts
  • packages/host-service/src/trpc/router/settings/index.ts
  • packages/host-service/src/trpc/router/settings/worktree-location.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/worktree-paths.ts
  • packages/host-service/src/trpc/router/workspaces/workspaces.ts
  • packages/host-service/test/integration/workspace-create-delete.integration.test.ts

Comment on lines +48 to +63
const existing = ctx.db
.select({ worktreeBaseDir: hostSettings.worktreeBaseDir })
.from(hostSettings)
.where(eq(hostSettings.id, HOST_SETTINGS_ID))
.get();
if (existing) return existing.worktreeBaseDir ?? null;

const legacyWorktreeBaseDir = getLegacyWorktreeBaseDir();
ctx.db
.insert(hostSettings)
.values({
id: HOST_SETTINGS_ID,
worktreeBaseDir: legacyWorktreeBaseDir,
})
.run();
return legacyWorktreeBaseDir;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make lazy initialization conflict-safe.

Two concurrent requests can both miss the row check and then both insert id = 1, causing a primary-key conflict on a read path.

💡 Suggested fix
 export function getHostWorktreeBaseDir(
 	ctx: Pick<HostServiceContext, "db">,
 ): string | null {
 	const existing = ctx.db
 		.select({ worktreeBaseDir: hostSettings.worktreeBaseDir })
 		.from(hostSettings)
 		.where(eq(hostSettings.id, HOST_SETTINGS_ID))
 		.get();
 	if (existing) return existing.worktreeBaseDir ?? null;

 	const legacyWorktreeBaseDir = getLegacyWorktreeBaseDir();
 	ctx.db
 		.insert(hostSettings)
 		.values({
 			id: HOST_SETTINGS_ID,
 			worktreeBaseDir: legacyWorktreeBaseDir,
 		})
+		.onConflictDoNothing({ target: hostSettings.id })
 		.run();
-	return legacyWorktreeBaseDir;
+	const current = ctx.db
+		.select({ worktreeBaseDir: hostSettings.worktreeBaseDir })
+		.from(hostSettings)
+		.where(eq(hostSettings.id, HOST_SETTINGS_ID))
+		.get();
+	return current?.worktreeBaseDir ?? legacyWorktreeBaseDir ?? null;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/host-service/src/trpc/router/settings/worktree-location.ts` around
lines 48 - 63, The lazy initialization in the settings read path can race: two
requests may both see no row and both execute the insert for hostSettings with
HOST_SETTINGS_ID causing a PK conflict; to fix, make the insertion idempotent
and conflict-safe by changing the logic around ctx.db.select/insert to either
perform an upsert/insert...on conflict do nothing (then re-select) or wrap the
check+insert in a DB transaction/lock so only one insert succeeds; use the
existing symbols hostSettings, HOST_SETTINGS_ID, ctx.db and
getLegacyWorktreeBaseDir() to locate and replace the current select/insert
sequence with an atomic upsert or a retry-after-no-op-insert-then-select flow.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 23, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 24 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Extracts useSetV2WorktreeBaseDir so the user/host sections share one
mutation hook, and drops the now-unused effectiveWorktreeBaseDir field
plus the getEffectiveWorktreeBaseDir helper — callers can resolve with
?? getHostWorktreeBaseDir(ctx).
The V2 Project settings page already had an inline dropdown for switching
between the hosts a project lives on; lift it into a shared HostSelect
under settings/components and use it in the Git settings Worktree
location section. When the user has access to more than one device, the
section now picks which device the setting applies to instead of being
implicitly the local machine.
Kitenite added 6 commits May 27, 2026 15:51
- Cut the 5-min GitHub-username cache in the host-service resolver. Workspace
  creates aren't a hot path and the cache was a stale-account footgun on
  `gh auth switch`. The renderer preview already has a 5-min `staleTime`.
- Single source for `BranchPrefixMode` — `@superset/local-db` re-exports from
  `@superset/shared/workspace-launch`. No more drift risk between the two.
- Extract `BranchPrefixControl` used by both `V2GitSettings` (host-wide) and
  `BranchPrefixSection` (per-project, `showDefault`). Empty custom-prefix on
  blur no longer fires a misleading `mode=custom, customPrefix=null` write.
…ngsRow

Mirror the V2ProjectSettings host-targeting pattern in V2GitSettings:
header + device picker on the right (shown only when 2+ devices),
SettingsRow body, all branchPrefix tRPC calls routed through
useHostUrl(hostId) so the chosen host's setting is what's edited.

Add ?hostId= search param to the /settings/git route. Promote
SettingsRow from v2-project-only to settings/components so both pages
share it.
The inline Select markup mirrored what HostSelect already does. Drop
the duplicate options interface and icon/dot rendering — V2GitSettings
now uses the same component V2ProjectSettings does.
Worktree-location and branch-prefix both add columns to the same
host_settings row + projects table. Replace the original 0005_big_mach_iv
migration with a single 0005_host_settings_and_project_overrides that
applies all four columns at once, so anyone applying this PR's migrations
gets one statement, not two.
…prefix

V2GitSettings was only rendering the branch-prefix row, so once the route
started serving the v2 page, the worktree-location knob from #4887
disappeared from the UI. Wire V2WorktreeLocationPicker into the same
SettingsRow layout — both rows now share the page's device picker.

(Side effect: UserWorktreeLocationSection's V2Body is no longer reached
from any v2 path. Leaving it for a follow-up clean-up so this commit
stays focused on the missing-row fix.)
Copy link
Copy Markdown
Contributor

@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: 4

🧹 Nitpick comments (4)
apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts (1)

1093-1093: ⚡ Quick win

Consider more generic description for cross-variant setting.

The description "Override the host worktree directory for this project" uses v2-specific terminology ("host worktree directory"), but this setting is marked as "shared" (line 178), meaning it appears in both v1 and v2 UIs. In v1, there is no host-level setting—only a user-level worktree location. A more generic description would be clearer for both variants, such as: "Override the default worktree directory for this project".

Suggested description
-		description: "Override the host worktree directory for this project",
+		description: "Override the default worktree directory for this project",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts`
at line 1093, The description for the shared setting currently reads "Override
the host worktree directory for this project" which is v2-specific; update the
description string in settings-search.ts for the shared setting (the entry whose
description currently equals "Override the host worktree directory for this
project") to a generic phrase such as "Override the default worktree directory
for this project" so it is appropriate for both v1 and v2 UIs.
plans/v2-branch-prefixes-design.md (2)

1-4: 💤 Low value

Document scope narrower than PR scope.

This document is titled "V2 configurable branch prefixes" and focuses exclusively on that feature, but the PR ("[codex] Add configurable worktree locations") implements both worktree base directory configuration and branch prefixes. Consider either:

  • Updating the title to reflect both features
  • Adding a note clarifying this doc covers only the branch-prefix aspect
  • Creating a companion design doc for the worktree-location feature
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plans/v2-branch-prefixes-design.md` around lines 1 - 4, This design doc
titled "V2 configurable branch prefixes" currently documents only branch-prefix
behavior while the associated PR ([codex] Add configurable worktree locations,
Ticket SUPER-835, branch configurable-branch-prefi) also implements configurable
worktree base directories; update the doc to either (a) change the title to
something like "V2 configurable branch prefixes and worktree locations", or (b)
add a clear scope section or note at the top stating the doc covers only
branch-prefixes and link to a new companion design doc for worktree-location,
and if choosing (b) create a separate design doc that documents the worktree
base directory design and link both docs together for clarity.

107-114: 💤 Low value

tRPC surface table is incomplete.

This table lists only the branch-prefix endpoints but omits the worktree-location endpoints also added in this PR (settings.worktreeLocation.get, settings.worktreeLocation.set, project.setWorktreeBaseDir). Consider documenting the complete tRPC surface or clarifying that this table shows only branch-prefix procedures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plans/v2-branch-prefixes-design.md` around lines 107 - 114, The tRPC surface
table currently lists only branch-prefix procedures; update it to either include
the worktree-location procedures or clarify it's partial: add rows for
settings.worktreeLocation.get, settings.worktreeLocation.set, and
project.setWorktreeBaseDir (with their return/argument shapes), or add a note
above the table stating it documents only branch-prefix endpoints and point to
where worktree endpoints are described; ensure the unique symbols
settings.worktreeLocation.get, settings.worktreeLocation.set, and
project.setWorktreeBaseDir are referenced so readers can locate the related
implementations.
packages/host-service/src/trpc/router/workspace-creation/utils/branch-prefix.test.ts (1)

56-140: ⚡ Quick win

Add one test for github mode resolution/fallback.

This suite is solid, but it currently misses the github mode path (including fallback behavior when GH username is unavailable). Adding one focused case would lock down the new precedence logic better.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/branch-prefix.test.ts`
around lines 56 - 140, Add a test in the existing
describe("resolveProjectBranchPrefix") block that exercises the "github" mode
and its fallback: call resolveProjectBranchPrefix twice using
makeCtx/createTestDb and makeProject({ branchPrefixMode: "github" }) — first
with a git fixture that exposes a GitHub username (use the existing git helper
that can provide a github username, e.g. gitWithGithub("octocat") or similar)
and expect the result to equal the formatted GitHub name ("octocat" or formatted
variant), then call it again with the git fixture lacking a GitHub username but
having an author name (gitWithGithub(null) / gitWithAuthor("Jane Doe")) and
expect it to fall back to the author-derived prefix ("Jane-Doe"); reference
resolveProjectBranchPrefix, makeProject, gitWithGithub/gitWithAuthor to locate
the right helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/git/components/V2GitSettings/V2GitSettings.tsx`:
- Around line 89-103: The branch-prefix queries are still running and the
control remains enabled even when the selected host is offline; update
branchPrefixQuery's enabled predicate to also require the host be online (e.g.,
enabled: !!targetHostUrl && gitInfoQuery.data?.online or gitInfoQuery.isSuccess)
so reads/writes don't run for offline hosts, and likewise guard the UI control
(the input/toggle rendered around lines referencing the branch prefix) so it is
disabled when gitInfoQuery indicates the host is offline (use gitInfoQuery.data
or gitInfoQuery.isSuccess/isLoading to determine online status); reference
branchPrefixQuery, gitInfoQuery, targetHostUrl and getHostServiceClientByUrl to
locate the code to change.

In `@apps/desktop/src/renderer/routes/_authenticated/settings/git/page.tsx`:
- Around line 11-13: In validateSearch, normalize hostId so empty or
whitespace-only strings are treated as absent: in the validateSearch function
(the validateSearch: (search: Record<string, unknown>): { hostId?: string } =>
...) check that search.hostId is a string and after trimming it's non-empty; if
trimmed is empty, return hostId as undefined, otherwise return the trimmed
string. Ensure you reference the validateSearch and hostId fields so callers
receive undefined for blank/whitespace values.

In `@packages/host-service/src/db/schema.ts`:
- Around line 55-66: The host_settings table promises a singleton row but the
schema (hostSettings, sqliteTable and its id column defined via
integer().primaryKey().default(1)) doesn't enforce id = 1; add a table-level
CHECK constraint (or column-level check) that enforces id = 1 so no other rows
can be inserted (e.g., add CONSTRAINT check_id_singleton CHECK (id = 1) or
equivalent via your schema builder) and keep the existing primaryKey/default
behavior on id.

In `@plans/v2-branch-prefixes-design.md`:
- Around line 55-63: Update the SQL snippet to include the missing
worktree_base_dir column in both host_settings and projects so the docs match
the actual migration/schema.ts; specifically, add worktree_base_dir (alongside
branch_prefix_mode and branch_prefix_custom) to the CREATE TABLE host_settings
block and add ALTER TABLE projects ADD worktree_base_dir text; so the symbols
worktree_base_dir, branch_prefix_mode, branch_prefix_custom, host_settings and
projects align with schema.ts.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts`:
- Line 1093: The description for the shared setting currently reads "Override
the host worktree directory for this project" which is v2-specific; update the
description string in settings-search.ts for the shared setting (the entry whose
description currently equals "Override the host worktree directory for this
project") to a generic phrase such as "Override the default worktree directory
for this project" so it is appropriate for both v1 and v2 UIs.

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/branch-prefix.test.ts`:
- Around line 56-140: Add a test in the existing
describe("resolveProjectBranchPrefix") block that exercises the "github" mode
and its fallback: call resolveProjectBranchPrefix twice using
makeCtx/createTestDb and makeProject({ branchPrefixMode: "github" }) — first
with a git fixture that exposes a GitHub username (use the existing git helper
that can provide a github username, e.g. gitWithGithub("octocat") or similar)
and expect the result to equal the formatted GitHub name ("octocat" or formatted
variant), then call it again with the git fixture lacking a GitHub username but
having an author name (gitWithGithub(null) / gitWithAuthor("Jane Doe")) and
expect it to fall back to the author-derived prefix ("Jane-Doe"); reference
resolveProjectBranchPrefix, makeProject, gitWithGithub/gitWithAuthor to locate
the right helpers.

In `@plans/v2-branch-prefixes-design.md`:
- Around line 1-4: This design doc titled "V2 configurable branch prefixes"
currently documents only branch-prefix behavior while the associated PR ([codex]
Add configurable worktree locations, Ticket SUPER-835, branch
configurable-branch-prefi) also implements configurable worktree base
directories; update the doc to either (a) change the title to something like "V2
configurable branch prefixes and worktree locations", or (b) add a clear scope
section or note at the top stating the doc covers only branch-prefixes and link
to a new companion design doc for worktree-location, and if choosing (b) create
a separate design doc that documents the worktree base directory design and link
both docs together for clarity.
- Around line 107-114: The tRPC surface table currently lists only branch-prefix
procedures; update it to either include the worktree-location procedures or
clarify it's partial: add rows for settings.worktreeLocation.get,
settings.worktreeLocation.set, and project.setWorktreeBaseDir (with their
return/argument shapes), or add a note above the table stating it documents only
branch-prefix endpoints and point to where worktree endpoints are described;
ensure the unique symbols settings.worktreeLocation.get,
settings.worktreeLocation.set, and project.setWorktreeBaseDir are referenced so
readers can locate the related implementations.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72badd78-fc6d-419a-98b8-3074d36104f3

📥 Commits

Reviewing files that changed from the base of the PR and between 965a714 and f3fc59a.

📒 Files selected for processing (25)
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/BranchPrefixControl/BranchPrefixControl.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/BranchPrefixControl/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsRow/SettingsRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsRow/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/V2GitSettings/V2GitSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/V2GitSettings/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/BranchPrefixSection/BranchPrefixSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/BranchPrefixSection/index.ts
  • packages/host-service/drizzle/0005_host_settings_and_project_overrides.sql
  • packages/host-service/drizzle/meta/0005_snapshot.json
  • packages/host-service/drizzle/meta/_journal.json
  • packages/host-service/src/db/schema.ts
  • packages/host-service/src/trpc/router/project/project.ts
  • packages/host-service/src/trpc/router/settings/branch-prefix.ts
  • packages/host-service/src/trpc/router/settings/index.ts
  • packages/host-service/src/trpc/router/workspace-creation/utils/branch-prefix.test.ts
  • packages/host-service/src/trpc/router/workspace-creation/utils/branch-prefix.ts
  • packages/host-service/src/trpc/router/workspaces/workspaces.ts
  • packages/local-db/src/schema/zod.ts
  • packages/shared/src/workspace-launch/branch.ts
  • plans/v2-branch-prefixes-design.md
  • plans/v2-branch-prefixes.md
✅ Files skipped from review due to trivial changes (5)
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/BranchPrefixControl/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/BranchPrefixSection/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/git/components/V2GitSettings/index.ts
  • packages/host-service/drizzle/0005_host_settings_and_project_overrides.sql
  • packages/host-service/drizzle/meta/_journal.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/host-service/drizzle/meta/0005_snapshot.json
  • packages/host-service/src/trpc/router/project/project.ts

Comment on lines +11 to +13
validateSearch: (search: Record<string, unknown>): { hostId?: string } => ({
hostId: typeof search.hostId === "string" ? search.hostId : undefined,
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize empty hostId query values to “absent”.

Line 12 treats hostId="" as valid, which can lead to inconsistent host targeting. Normalize blank/whitespace values to undefined.

💡 Suggested fix
 export const Route = createFileRoute("/_authenticated/settings/git/")({
 	component: GitSettingsPage,
-	validateSearch: (search: Record<string, unknown>): { hostId?: string } => ({
-		hostId: typeof search.hostId === "string" ? search.hostId : undefined,
-	}),
+	validateSearch: (search: Record<string, unknown>): { hostId?: string } => {
+		const hostId =
+			typeof search.hostId === "string" ? search.hostId.trim() : "";
+		return {
+			hostId: hostId.length > 0 ? hostId : undefined,
+		};
+	},
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validateSearch: (search: Record<string, unknown>): { hostId?: string } => ({
hostId: typeof search.hostId === "string" ? search.hostId : undefined,
}),
export const Route = createFileRoute("/_authenticated/settings/git/")({
component: GitSettingsPage,
validateSearch: (search: Record<string, unknown>): { hostId?: string } => {
const hostId =
typeof search.hostId === "string" ? search.hostId.trim() : "";
return {
hostId: hostId.length > 0 ? hostId : undefined,
};
},
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/routes/_authenticated/settings/git/page.tsx` around
lines 11 - 13, In validateSearch, normalize hostId so empty or whitespace-only
strings are treated as absent: in the validateSearch function (the
validateSearch: (search: Record<string, unknown>): { hostId?: string } => ...)
check that search.hostId is a string and after trimming it's non-empty; if
trimmed is empty, return hostId as undefined, otherwise return the trimmed
string. Ensure you reference the validateSearch and hostId fields so callers
receive undefined for blank/whitespace values.

Comment on lines +55 to +66
/**
* Single-row host-wide settings (always `id = 1`). The host-service has no
* generic settings store yet; this row holds host-wide knobs (worktree base
* dir, branch-prefix default) that projects fall back to when they have no
* override of their own.
*/
export const hostSettings = sqliteTable("host_settings", {
id: integer().primaryKey().default(1),
worktreeBaseDir: text("worktree_base_dir"),
branchPrefixMode: text("branch_prefix_mode").$type<BranchPrefixMode>(),
branchPrefixCustom: text("branch_prefix_custom"),
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce the singleton invariant for host_settings at the schema level.

The comment says this table is always single-row (id = 1), but the schema does not enforce that. If any non-1 row is inserted, callers using unscoped .get() can read the wrong row.

Suggested direction
- export const hostSettings = sqliteTable("host_settings", {
+ export const hostSettings = sqliteTable("host_settings", {
    id: integer().primaryKey().default(1),
    worktreeBaseDir: text("worktree_base_dir"),
    branchPrefixMode: text("branch_prefix_mode").$type<BranchPrefixMode>(),
    branchPrefixCustom: text("branch_prefix_custom"),
- });
+ }, (table) => [
+   // enforce singleton semantics at DB level (id must be 1)
+   // add CHECK constraint via migration/schema helper used in this repo
+ ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/host-service/src/db/schema.ts` around lines 55 - 66, The
host_settings table promises a singleton row but the schema (hostSettings,
sqliteTable and its id column defined via integer().primaryKey().default(1))
doesn't enforce id = 1; add a table-level CHECK constraint (or column-level
check) that enforces id = 1 so no other rows can be inserted (e.g., add
CONSTRAINT check_id_singleton CHECK (id = 1) or equivalent via your schema
builder) and keep the existing primaryKey/default behavior on id.

Comment on lines +55 to +63
```sql
CREATE TABLE host_settings (
id integer PRIMARY KEY DEFAULT 1 NOT NULL,
branch_prefix_mode text,
branch_prefix_custom text
);
ALTER TABLE projects ADD branch_prefix_mode text;
ALTER TABLE projects ADD branch_prefix_custom text;
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

SQL schema snippet is incomplete.

The schema shown omits the worktree_base_dir column that is also added to both host_settings and projects tables in the same migration. According to the actual schema (context snippet from schema.ts), both tables include worktreeBaseDir alongside the branch-prefix fields. Since this PR adds both configurable worktree locations and branch prefixes, the documentation should reflect the complete schema.

📝 Complete schema representation
CREATE TABLE host_settings (
    id integer PRIMARY KEY DEFAULT 1 NOT NULL,
    worktree_base_dir text,
    branch_prefix_mode text,
    branch_prefix_custom text
);
ALTER TABLE projects ADD worktree_base_dir text;
ALTER TABLE projects ADD branch_prefix_mode text;
ALTER TABLE projects ADD branch_prefix_custom text;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plans/v2-branch-prefixes-design.md` around lines 55 - 63, Update the SQL
snippet to include the missing worktree_base_dir column in both host_settings
and projects so the docs match the actual migration/schema.ts; specifically, add
worktree_base_dir (alongside branch_prefix_mode and branch_prefix_custom) to the
CREATE TABLE host_settings block and add ALTER TABLE projects ADD
worktree_base_dir text; so the symbols worktree_base_dir, branch_prefix_mode,
branch_prefix_custom, host_settings and projects align with schema.ts.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 26 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/host-service/src/trpc/router/settings/branch-prefix.ts">

<violation number="1" location="packages/host-service/src/trpc/router/settings/branch-prefix.ts:18">
P2: Read the host settings row by `id = 1` instead of unfiltered `.get()` to avoid returning the wrong settings record.</violation>

<violation number="2" location="packages/host-service/src/trpc/router/settings/branch-prefix.ts:30">
P2: Validate that `customPrefix` is present and non-empty when `mode` is `custom` to prevent inconsistent branch-prefix settings.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

.input(
z.object({
mode: z.enum(BRANCH_PREFIX_MODES),
customPrefix: z.string().nullable().optional(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Validate that customPrefix is present and non-empty when mode is custom to prevent inconsistent branch-prefix settings.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/host-service/src/trpc/router/settings/branch-prefix.ts, line 30:

<comment>Validate that `customPrefix` is present and non-empty when `mode` is `custom` to prevent inconsistent branch-prefix settings.</comment>

<file context>
@@ -0,0 +1,59 @@
+		.input(
+			z.object({
+				mode: z.enum(BRANCH_PREFIX_MODES),
+				customPrefix: z.string().nullable().optional(),
+			}),
+		)
</file context>

export const branchPrefixRouter = router({
/** The host-wide default. `none` when never configured. */
get: protectedProcedure.query(({ ctx }) => {
const row = ctx.db.select().from(hostSettings).get();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Read the host settings row by id = 1 instead of unfiltered .get() to avoid returning the wrong settings record.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/host-service/src/trpc/router/settings/branch-prefix.ts, line 18:

<comment>Read the host settings row by `id = 1` instead of unfiltered `.get()` to avoid returning the wrong settings record.</comment>

<file context>
@@ -0,0 +1,59 @@
+export const branchPrefixRouter = router({
+	/** The host-wide default. `none` when never configured. */
+	get: protectedProcedure.query(({ ctx }) => {
+		const row = ctx.db.select().from(hostSettings).get();
+		return {
+			mode: (row?.branchPrefixMode ?? "none") satisfies BranchPrefixMode,
</file context>

Avoids failing fetches and error toasts when the picker targets an offline
remote host; mirrors the existing worktree-query gating.
@Kitenite Kitenite merged commit f1d5a67 into main May 28, 2026
16 of 17 checks passed
@Kitenite Kitenite deleted the restore-worktrees-locatio branch May 28, 2026 07:50
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