Skip to content

refactor(desktop): derive v2 base branch from git config#3492

Merged
Kitenite merged 6 commits into
mainfrom
lithe-squirrel
Apr 15, 2026
Merged

refactor(desktop): derive v2 base branch from git config#3492
Kitenite merged 6 commits into
mainfrom
lithe-squirrel

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 15, 2026

Summary

The v2 right sidebar always rendered the repo default base branch. Two independent bugs caused it:

  1. Display bugBaseBranchSelector's currentValue was hardcoded to defaultBranchName in ChangesHeader.tsx, so the user's selection never appeared in the UI.
  2. Persistence bug — v2 kept its own sidebarState.baseBranch field on v2WorkspaceLocalState, never seeded at creation and divergent from the v1 DB / git-config source of truth.

Fix

  • Display: thread the current base branch through useChangesTabChangesTabContentChangesHeaderBaseBranchSelector so the selector renders the real value (falls back to defaultBranchName when unset).
  • Persistence: drop sidebarState.baseBranch entirely. Add two host-service procedures, git.getBaseBranch (reads branch.<current>.base git config) and git.setBaseBranch (writes it). V2's three readers (useChangesTab, useGitStatus, useSidebarDiffRef) use the query; the selector writes via the mutation and invalidates dependent queries.

Why git config as the store

  • Same key (branch.<name>.base) v1 already uses via getBranchBaseConfig, so v1 and v2 stay aligned without bridging code.
  • Per-branch storage — survives worktree recreates and follows branch renames in git.
  • No seed-at-creation workaround needed; whoever creates the branch (workspace-init.ts) already writes the config.
  • React Query handles caching; base branch changes rarely enough that refetches are imperceptible.

Test plan

  • Create a new v2 workspace via fork with a non-default base branch → sidebar shows the selected branch, commit list compares against it.
  • Change the base branch via the selector → selector updates, getStatus/listCommits refetch against the new base.
  • Close and reopen the workspace → selection persists (git config, not local state).
  • V1 UI still reads/writes the same git config key correctly (no regression).
  • bun run typecheck --filter=@superset/desktop --filter=@superset/host-service passes.

Summary by CodeRabbit

  • New Features

    • UI now reads a remotely-stored base branch so the selected base branch reflects workspace git config.
    • Base branch can be saved remotely and is surfaced across Changes and Diff views.
    • New worktree creation records the fork base so new workspaces inherit the base branch.
  • Refactor

    • Base branch settings now persist in git configuration instead of only local app state.
    • Improved consistency of base branch selection across workspace operations.

The v2 right sidebar always rendered the repo default branch because the
BaseBranchSelector was hardcoded to display defaultBranchName, and
ensureSidebarWorkspaceRecord never seeded sidebarState.baseBranch from
the pending workspace's selection. Thread the persisted value into the
selector and seed it at creation time from pending.baseBranch.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 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
📝 Walkthrough

Walkthrough

This PR moves workspace base-branch state from local persisted sidebar state to remote TRPC-backed storage, adds TRPC endpoints to read/write branch.<current>.base in git config, updates hooks to query that value and invalidate caches on change, threads the value through components, and removes the local schema field.

Changes

Cohort / File(s) Summary
TRPC Backend - Git Endpoints
packages/host-service/src/trpc/router/git/git.ts
Added protected procedures getBaseBranch and setBaseBranch to read/write branch.<currentBranch>.base in git config; setBaseBranch errors with PRECONDITION_FAILED on detached HEAD.
Workspace Creation
packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
When creating or checking out worktrees, set per-worktree push.autoSetupRemote true and record branch.<branchName>.base = startPoint.shortName for non-head starts; failures are warned but non-fatal.
Local State Schema Cleanup
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
Removed sidebarState.baseBranch from the persisted workspace local state schema.
Hooks — useChangesTab & setBaseBranch mutation
.../useChangesTab/useChangesTab.tsx
Replaced local-state baseBranch with workspaceTrpc.git.getBaseBranch.useQuery; setBaseBranch now calls TRPC mutation and invalidates getBaseBranch, getStatus, listCommits, getDiff; threads baseBranch to UI.
Hooks — useGitStatus & useSidebarDiffRef
apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts, .../useSidebarDiffRef/useSidebarDiffRef.ts
Removed local-state dependency; derive baseBranch via workspaceTrpc.git.getBaseBranch.useQuery and feed it into status/diff logic.
Components — Prop Threading
apps/desktop/src/renderer/.../ChangesHeader/ChangesHeader.tsx, .../ChangesTabContent/ChangesTabContent.tsx
Added `baseBranch: string

Sequence Diagram

sequenceDiagram
    participant UI as UI Components
    participant Hook as Hooks (useChangesTab / useGitStatus)
    participant TRPC as TRPC Client
    participant Server as Host-Service Server
    participant Git as Git Config

    UI->>Hook: render / request baseBranch
    Hook->>TRPC: getBaseBranch.useQuery({ workspaceId })
    TRPC->>Server: RPC getBaseBranch
    Server->>Git: read git config branch.<current>.base
    Git-->>Server: baseBranch or null
    Server-->>TRPC: { baseBranch }
    TRPC-->>Hook: query result (staleTime: ∞)
    Hook->>UI: pass baseBranch prop
    UI->>UI: render selector with baseBranch

    Note over UI,Hook: user updates base branch
    UI->>Hook: call setBaseBranch({ baseBranch })
    Hook->>TRPC: setBaseBranch.mutate({ workspaceId, baseBranch })
    TRPC->>Server: RPC setBaseBranch
    Server->>Git: git config --set/--unset branch.<current>.base
    Git-->>Server: success
    Server-->>TRPC: { baseBranch }
    TRPC->>Hook: mutation complete
    TRPC->>TRPC: invalidate getBaseBranch, getStatus, listCommits, getDiff
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

🐇
I hopped from burrow local to sky,
Stored base in git where branches lie,
TRPC hums and caches sing,
A rabbit’s tweak — a tiny spring,
I nibble logs and watch it fly.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main refactoring: moving base branch derivation from local state to git config, which directly addresses both display and persistence bugs documented in the PR description.
Description check ✅ Passed The PR description comprehensively covers the bugs being fixed, the implementation approach, and rationale. It follows the template structure with clear sections for Summary, Fix, and Test plan, providing sufficient detail for code review.

✏️ 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 lithe-squirrel

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 15, 2026

Greptile Summary

This PR fixes two independent bugs in the v2 sidebar's base branch handling: a display bug where BaseBranchSelector was hardcoded to defaultBranchName even when a different value was persisted, and a creation bug where ensureSidebarWorkspaceRecord never seeded sidebarState.baseBranch from the fork-time pendingWorkspaces.baseBranch.

Key changes:

  • baseBranch is now threaded from useChangesTabChangesTabContentChangesHeaderBaseBranchSelector, with baseBranch ?? defaultBranchName providing a transparent fallback for null (legacy/unset) workspaces without overwriting the stored value.
  • ensureSidebarWorkspaceRecord and ensureWorkspaceInSidebar accept an optional baseBranch?: string | null parameter, defaulting to null; the pending page now passes pending.baseBranch so the fork-time selection is immediately persisted.
  • A new V2_BASE_BRANCH.md doc clearly explains the V1/V2 storage split, seeding contract, and rationale for keeping them separate.
  • The baseBranch defaults to null for other callers (layout.tsx, useV2WorkspacePaneLayout, V2WorkspaceRow) which is correct since ensureSidebarWorkspaceRecord exits early if a record already exists.

Confidence Score: 5/5

This PR is safe to merge — both bugs are precisely fixed, backward compatibility is maintained, and all other callers of ensureWorkspaceInSidebar remain correct via the null default.

The two bugs are tightly scoped and independently fixed. The display fix (baseBranch ?? defaultBranchName) is a one-line change that correctly handles null. The creation fix threads pending.baseBranch down exactly one call site. The guard in ensureSidebarWorkspaceRecord (if get(workspaceId) return) ensures all other callers don't interfere. The schema already had baseBranch: z.string().nullable().default(null), so no migration is needed. Documentation is thorough.

No files require special attention.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts Core creation bug fix: ensureSidebarWorkspaceRecord and ensureWorkspaceInSidebar now accept baseBranch: string
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesHeader/ChangesHeader.tsx Core display bug fix: replaces hardcoded defaultBranchName with baseBranch ?? defaultBranchName in BaseBranchSelector's currentValue prop, and adds baseBranch: string
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx Passes pending.baseBranch (type string
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesTabContent/ChangesTabContent.tsx Pass-through component updated to accept and forward baseBranch: string
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx Reads sidebarState.baseBranch from the collection and threads it to ChangesTabContent; also uses it in listCommits query (existing correct behavior).
apps/desktop/docs/V2_BASE_BRANCH.md New documentation clearly explaining V2 base branch storage, seeding contract, and rationale for the V1/V2 split.

Sequence Diagram

sequenceDiagram
    participant Pending as PendingWorkspacePage
    participant DSS as useDashboardSidebarState
    participant Col as v2WorkspaceLocalState Collection
    participant UCT as useChangesTab
    participant CH as ChangesHeader
    participant BBS as BaseBranchSelector

    Note over Pending: workspace.status === "succeeded"
    Pending->>DSS: ensureWorkspaceInSidebar(workspaceId, projectId, baseBranch)
    DSS->>Col: insert sidebarState with baseBranch
    Note over Col: Record seeded with fork-time baseBranch
    Pending->>Pending: navigate to /v2-workspace/$workspaceId
    UCT->>Col: get(workspaceId).sidebarState.baseBranch
    Col-->>UCT: baseBranch (string | null)
    UCT->>CH: baseBranch={baseBranch}
    CH->>BBS: currentValue={baseBranch ?? defaultBranchName}
    Note over BBS: Displays persisted branch
    BBS-->>UCT: onChange(newBranch)
    UCT->>Col: update sidebarState.baseBranch = newBranch
    Note over Col: Selection persisted
Loading

Reviews (1): Last reviewed commit: "fix(desktop): persist v2 sidebar base br..." | Re-trigger Greptile

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.

No issues found across 6 files

…toring

Replace sidebarState.baseBranch in v2WorkspaceLocalState with a
host-service git.getBaseBranch query backed by git config
(branch.<name>.base, the same key v1 uses). Writes go through a new
git.setBaseBranch mutation that invalidates the dependent queries.

Removes the duplicate store (and the seed-at-creation workaround) so
the v2 sidebar reflects whatever git has, which is already per-branch
and persists across worktree recreates. React Query's cache is the
only cache we need — base branch changes rarely enough that an extra
roundtrip on focus is imperceptible.
@Kitenite Kitenite changed the title fix(desktop): persist v2 sidebar base branch across creation and display refactor(desktop): derive v2 base branch from git config Apr 15, 2026
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts (1)

30-34: ⚠️ Potential issue | 🟠 Major

Invalidate getBaseBranch on git:changed to handle external branch switches.

When the user changes branches outside the app (e.g., via terminal), the git:changed event fires but only getStatus is invalidated. Since getBaseBranch reads the config for the current branch and uses staleTime: Number.POSITIVE_INFINITY, the cached result becomes stale after an external branch switch—the UI would show the new branch name but the old branch's base.

🐛 Proposed fix to invalidate getBaseBranch on git:changed
 const invalidate = useCallback(() => {
   void utils.git.getStatus.invalidate({ workspaceId });
+  void utils.git.getBaseBranch.invalidate({ workspaceId });
 }, [utils, workspaceId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts`
around lines 30 - 34, The git:changed handler currently only invalidates
utils.git.getStatus via the invalidate callback; update that callback (used by
useWorkspaceEvent("git:changed", ...)) to also invalidate
utils.git.getBaseBranch so external branch switches refresh the base-branch
cache. Locate the invalidate function in useGitStatus (the one calling
utils.git.getStatus.invalidate with workspaceId) and add a second invalidate
call to utils.git.getBaseBranch.invalidate passing the same workspaceId (or
appropriate params) so both caches are invalidated on the git:changed event.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts (1)

19-27: Consider sequencing to avoid double-fetch of getStatus.

Currently both queries run in parallel on mount. If a non-null baseBranch is configured, getStatus will fetch twice: once with undefined (while baseBranchQuery is loading), then again with the resolved value when the query key changes.

This is minor since it's only on initial mount, but you could avoid the extra request by gating getStatus on baseBranchQuery completion:

♻️ Optional: wait for baseBranch before fetching status
 const baseBranchQuery = workspaceTrpc.git.getBaseBranch.useQuery(
   { workspaceId },
   { staleTime: Number.POSITIVE_INFINITY, enabled: Boolean(workspaceId) },
 );
 const baseBranch = baseBranchQuery.data?.baseBranch ?? null;
+const baseBranchReady = baseBranchQuery.isSuccess;

 const query = workspaceTrpc.git.getStatus.useQuery(
   { workspaceId, baseBranch: baseBranch ?? undefined },
-  { refetchOnWindowFocus: true, enabled: Boolean(workspaceId) },
+  { refetchOnWindowFocus: true, enabled: Boolean(workspaceId) && baseBranchReady },
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts`
around lines 19 - 27, The getStatus query is firing twice because it runs before
getBaseBranch (baseBranchQuery) settles; change the
workspaceTrpc.git.getStatus.useQuery call to wait for baseBranchQuery completion
by adding its state to the enabled option (e.g., enabled: Boolean(workspaceId)
&& baseBranchQuery.isSuccess or baseBranchQuery.isFetched) so getStatus only
runs after baseBranchQuery resolves, and keep passing baseBranch ?? undefined
for the query variables; update the enabled logic in the getStatus useQuery
invocation accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts`:
- Around line 30-34: The git:changed handler currently only invalidates
utils.git.getStatus via the invalidate callback; update that callback (used by
useWorkspaceEvent("git:changed", ...)) to also invalidate
utils.git.getBaseBranch so external branch switches refresh the base-branch
cache. Locate the invalidate function in useGitStatus (the one calling
utils.git.getStatus.invalidate with workspaceId) and add a second invalidate
call to utils.git.getBaseBranch.invalidate passing the same workspaceId (or
appropriate params) so both caches are invalidated on the git:changed event.

---

Nitpick comments:
In `@apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts`:
- Around line 19-27: The getStatus query is firing twice because it runs before
getBaseBranch (baseBranchQuery) settles; change the
workspaceTrpc.git.getStatus.useQuery call to wait for baseBranchQuery completion
by adding its state to the enabled option (e.g., enabled: Boolean(workspaceId)
&& baseBranchQuery.isSuccess or baseBranchQuery.isFetched) so getStatus only
runs after baseBranchQuery resolves, and keep passing baseBranch ?? undefined
for the query variables; update the enabled logic in the getStatus useQuery
invocation accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 908dc355-8bf3-4f7b-8dd1-af692a595df2

📥 Commits

Reviewing files that changed from the base of the PR and between 53f0d97 and b33a914.

📒 Files selected for processing (5)
  • apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useSidebarDiffRef/useSidebarDiffRef.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
  • packages/host-service/src/trpc/router/git/git.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 15, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

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.

4 issues found across 8 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/git/git.ts">

<violation number="1" location="packages/host-service/src/trpc/router/git/git.ts:281">
P2: Do not swallow errors when unsetting the base branch config; this can report success even when persistence fails.

(Based on your team's feedback about handling async errors explicitly and avoiding silent empty catches.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts">

<violation number="1" location="apps/desktop/src/renderer/hooks/host-service/useGitStatus/useGitStatus.ts:21">
P1: `getBaseBranch` is cached with infinite staleness but never invalidated on `git:changed`, so branch switches can leave status comparisons using a stale base branch.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useSidebarDiffRef/useSidebarDiffRef.ts">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useSidebarDiffRef/useSidebarDiffRef.ts:22">
P2: Caching `git.getBaseBranch` with infinite staleness can keep an outdated base branch after checkout changes, so sidebar diffs may compare against the wrong base.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx:35">
P2: `getBaseBranch` is cached forever here, but this value depends on the current checked-out branch and is not invalidated on general git change events. That can leave the sidebar/commit comparison using an outdated base branch after branch switches.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

} else {
await git
.raw(["config", "--unset", `branch.${currentBranch}.base`])
.catch(() => {});
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 15, 2026

Choose a reason for hiding this comment

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

P2: Do not swallow errors when unsetting the base branch config; this can report success even when persistence fails.

(Based on your team's feedback about handling async errors explicitly and avoiding silent empty catches.)

View Feedback

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/git/git.ts, line 281:

<comment>Do not swallow errors when unsetting the base branch config; this can report success even when persistence fails.

(Based on your team's feedback about handling async errors explicitly and avoiding silent empty catches.) </comment>

<file context>
@@ -231,6 +231,58 @@ export const gitRouter = router({
+			} else {
+				await git
+					.raw(["config", "--unset", `branch.${currentBranch}.base`])
+					.catch(() => {});
+			}
+			return { baseBranch: input.baseBranch };
</file context>
Suggested change
.catch(() => {});
.catch((error) => {
if (`${error}`.includes("No such section or key")) return;
throw error;
});
Fix with Cubic


const baseBranchQuery = workspaceTrpc.git.getBaseBranch.useQuery(
{ workspaceId },
{ staleTime: Number.POSITIVE_INFINITY },
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 15, 2026

Choose a reason for hiding this comment

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

P2: Caching git.getBaseBranch with infinite staleness can keep an outdated base branch after checkout changes, so sidebar diffs may compare against the wrong base.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useSidebarDiffRef/useSidebarDiffRef.ts, line 22:

<comment>Caching `git.getBaseBranch` with infinite staleness can keep an outdated base branch after checkout changes, so sidebar diffs may compare against the wrong base.</comment>

<file context>
@@ -15,7 +16,12 @@ export function useSidebarDiffRef(workspaceId: string): DiffRef {
+
+	const baseBranchQuery = workspaceTrpc.git.getBaseBranch.useQuery(
+		{ workspaceId },
+		{ staleTime: Number.POSITIVE_INFINITY },
+	);
+	const baseBranch = baseBranchQuery.data?.baseBranch ?? null;
</file context>
Fix with Cubic


const baseBranchQuery = workspaceTrpc.git.getBaseBranch.useQuery(
{ workspaceId },
{ staleTime: Number.POSITIVE_INFINITY },
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 15, 2026

Choose a reason for hiding this comment

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

P2: getBaseBranch is cached forever here, but this value depends on the current checked-out branch and is not invalidated on general git change events. That can leave the sidebar/commit comparison using an outdated base branch after branch switches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/useChangesTab.tsx, line 35:

<comment>`getBaseBranch` is cached forever here, but this value depends on the current checked-out branch and is not invalidated on general git change events. That can leave the sidebar/commit comparison using an outdated base branch after branch switches.</comment>

<file context>
@@ -24,12 +24,17 @@ export function useChangesTab({
+
+	const baseBranchQuery = workspaceTrpc.git.getBaseBranch.useQuery(
+		{ workspaceId },
+		{ staleTime: Number.POSITIVE_INFINITY },
+	);
+	const baseBranch = baseBranchQuery.data?.baseBranch ?? null;
</file context>
Fix with Cubic

The v2 `create` mutation used the selected base branch only as the
`git worktree add` start point and never wrote it to
`branch.<newBranch>.base`. Combined with the v2 sidebar deriving the
base branch from that config, a freshly forked workspace's Changes tab
always compared against the repo default — the user's fork-time
selection was effectively dropped the moment creation finished.

Record `startPoint.shortName` into the config right after the worktree
is created. Skipped for "head" start points where no meaningful base
exists.
V2's create and checkout mutations produced worktrees without
push.autoSetupRemote set, so the first terminal `git push` failed with
"no upstream branch" unless the user had the config globally. V1's
equivalent path already writes this per-worktree; match that.

Safe against wrong upstream targeting: --no-track (create) guarantees
no upstream at first push, so auto-create always wins and always sets
<remote>/<branch-name>, never the base branch. Checkout from a remote
branch already has upstream via --track, so the config is a no-op
there.

Also rewrite the --no-track comment at the create site — it had
incorrectly tied --no-track to push targeting, when --no-track is
actually about suppressing `git pull` / ahead-behind counts against
the start point; push targeting is governed independently by
push.autoSetupRemote.
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 1 file (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/workspace-creation/workspace-creation.ts">

<violation number="1" location="packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts:669">
P2: Using `git config --local` here applies `push.autoSetupRemote` to the entire repository, not just the new worktree. That introduces cross-worktree side effects and does not match the per-worktree behavior described in the comment.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

…fig scope

Address PR review:

1. P1 (CodeRabbit / cubic): getBaseBranch was cached with staleTime:
   Infinity but never invalidated on git:changed. An external branch
   switch (terminal `git checkout`) would leave the sidebar comparing
   against the previous branch's base. Add invalidation alongside
   getStatus in useGitStatus — since useGitStatus is always mounted
   while the Changes UI is visible, this refreshes the shared cache
   for all consumers (useChangesTab, useSidebarDiffRef).

2. P2 (cubic): my earlier comment claimed push.autoSetupRemote was
   set "per-worktree" but `git config --local` in a linked worktree
   writes to the shared repo config, not a worktree-specific one.
   Rewrite the comments to note the repo-wide scope is intentional —
   every superset-managed worktree wants the same ergonomics.
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