Skip to content

fix(desktop): prevent [gone] from being stored as workspace branch name#2885

Merged
Kitenite merged 7 commits into
mainfrom
fix/gone-branch-parsing
Mar 26, 2026
Merged

fix(desktop): prevent [gone] from being stored as workspace branch name#2885
Kitenite merged 7 commits into
mainfrom
fix/gone-branch-parsing

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Mar 25, 2026

Summary

  • Parser fix: parsePortelainStatus incorrectly extracted [gone] as the branch name when git reported ## No commits yet on BRANCH...origin/BRANCH [gone]. The old code split by space and took the last element; the fix strips the prefix and applies the same tracking-info regex used for normal branches.
  • Validation: syncBranch mutation now rejects branch names starting with [ or containing spaces, preventing git status artifacts from being persisted.
  • Startup repair: One-time repair on app start reads the real branch from each worktree's git HEAD file for any existing corrupted [gone] records.

Root cause

When a worktree's remote tracking branch is deleted (PR merged) and the worktree is in a "No commits yet" state, git status --porcelain=v1 -b outputs:

## No commits yet on my-branch...origin/my-branch [gone]

The parser split by space and took the last word ([gone]) instead of parsing the actual branch name (my-branch). useBranchSyncInvalidation then synced this invalid value into the database.

Test plan

  • Verify workspaces that previously showed [gone] now display the correct branch name after app restart
  • Create a worktree workspace, delete the remote branch, verify the branch name is still correctly displayed
  • Verify syncBranch rejects [gone] and other invalid branch names

Summary by cubic

Fixes a bug that stored “[gone]” as the workspace branch when parsing git status for new branches with a deleted remote. Updates the parser and adds stricter branch validation.

  • Bug Fixes
    • Parse “No commits yet on BRANCH...origin/BRANCH [gone]” correctly by stripping the prefix and reusing the tracking regex to extract the real branch.
    • Reject invalid branch names in syncBranch (empty, HEAD, starts with “[”, or contains spaces).

Written for commit 0e56023. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • More accurate repository status and ahead/behind reporting via updated git status parsing.
  • Improvements

    • Stricter branch-name validation (rejects empty, "HEAD", names starting with "[" or containing spaces).
    • More robust handling of detached/unavailable HEAD and safer branch resolution across git operations.
    • Preview-deploy lookup now gracefully falls back when commit SHA is unavailable.
  • Tests

    • Added tests for status parsing, merge PR flows, and branch-sync scenarios.

The porcelain status parser incorrectly extracted "[gone]" as the branch
name when git reported "No commits yet on BRANCH...origin/BRANCH [gone]".
The old code split by space and took the last element; the fix strips the
prefix and applies the same tracking-info regex used for normal branches.

Also adds syncBranch validation to reject obviously invalid names and a
one-time startup repair that reads the real branch from the worktree HEAD
file for any existing corrupted records.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27672f45-7078-4b14-8674-e0754bfa08a0

📥 Commits

Reviewing files that changed from the base of the PR and between bd08307 and 2c11c51.

📒 Files selected for processing (1)
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts

📝 Walkthrough

Walkthrough

Centralizes branch/HEAD resolution with getCurrentBranch/getHeadSha, upgrades git status parsing to porcelain v2, tightens branch-name validation in syncBranch, and adds detached-HEAD handling and tests across git, PR, and workspace flows.

Changes

Cohort / File(s) Summary
Branch Validation
apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
syncBranch now rejects empty/"HEAD" branch names and also rejects names that start with [ or contain spaces; returns { success: false, reason: "invalid-branch" } for those cases.
Status Parsing (porcelain v2)
apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts, apps/desktop/src/lib/trpc/routers/workspaces/utils/git.test.ts
getStatusNoLock uses --porcelain=v2 --branch; replaces the v1 parser with exported parsePorcelainStatusV2 and adds isUnbornHeadError. Parser reads # branch.* headers and v2 records (1,2,u,?,!) to populate current/tracking/detached, ahead/behind, files, staged/modified/renamed/conflicted sets.
Branch Resolution Refactor
apps/desktop/src/lib/trpc/routers/changes/git-operations.ts, apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts, apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts, apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts, packages/host-service/src/runtime/pull-requests/pull-requests.ts
Replaces direct git rev-parse --abbrev-ref HEAD with getCurrentBranch(worktreePath)/getCurrentBranchName(); adds early exits when branch is falsy, makes head SHA nullable and tolerant of unborn-HEAD errors, and surfaces detached-HEAD errors where appropriate.
Git/PR Workflows & Tests
apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts, apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.test.ts, apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
mergePullRequest uses getCurrentBranch; when branch is falsy, falls back to legacy gh merge flow for detached worktrees; head SHA handling is tolerant of unborn HEAD and may be undefined; tests added to cover branches, unborn HEAD, and error fallbacks.
Host-service branch sync & tests
packages/host-service/src/runtime/pull-requests/pull-requests.ts, packages/host-service/test/pull-requests.test.ts
Adds getCurrentBranchName() and getHeadSha() helpers; syncWorkspaceBranches() skips/continues when branch cannot be determined, writes null headSha for unborn HEAD, and tests unborn/unexpected HEAD failure behaviors.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant Git as Git CLI
    participant GitHub
    participant DB

    Client->>Server: trigger operation (push/sync/fetch/createPR/merge)
    Server->>Git: getCurrentBranch(worktreePath)
    Git-->>Server: branch name OR null
    alt branch == null (detached / cannot resolve)
        Server->>Git: run legacy gh/git commands using worktree cwd
        Git-->>Server: command result
        Server->>GitHub: (optional) fetch/report via gh CLI
        GitHub-->>Server: result
        Server->>Client: return success or TRPCError per flow
    else branch resolved
        Server->>Git: perform git operations (fetch/push, rev-parse HEAD)
        Git-->>Server: headSha or error (unborn handled as undefined)
        Server->>DB: update workspace.branch / headSha if changed
        DB-->>Server: confirmation
        Server->>GitHub: fetch PR/status/preview (headSha optional)
        GitHub-->>Server: result
        Server->>Client: return success
    end
Loading
sequenceDiagram
    participant Caller as getStatusNoLock()
    participant Git as Git CLI
    participant Parser as parsePorcelainStatusV2

    Caller->>Git: git status --porcelain=v2 --branch
    Git-->>Caller: stdout (porcelain v2)
    Caller->>Parser: parse stdout
    Parser->>Parser: read `# branch.*` headers (head, upstream, ab)
    Parser->>Parser: parse file records (1,2,u,?,!)
    Parser-->>Caller: StatusResult { files, staged, modified, renamed, conflicted, ahead, behind }
    Caller-->>Caller: return status to caller
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I nibble at branches, neat and spry,
I parse v2 headers up in the sky,
When HEAD is unborn I tiptoe light,
No brackets or spaces—branches behave right,
Hooray — Git hops smoother by moonlight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% 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 clearly describes the main fix: preventing '[gone]' from being stored as a workspace branch name, which matches the primary objective of the changeset.
Description check ✅ Passed The description includes a clear summary of changes, root cause explanation, test plan, and type of change (bug fix). It adequately covers all the required sections from the template.

✏️ 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 fix/gone-branch-parsing

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

❤️ Share

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

Copy link
Copy Markdown
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 3 files

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="apps/desktop/src/main/lib/local-db/index.ts">

<violation number="1" location="apps/desktop/src/main/lib/local-db/index.ts:125">
P2: Don't silently swallow per-worktree repair errors; log a warning with worktree context so failed repairs are diagnosable.

(Based on your team's feedback about avoiding empty catch blocks that hide failures.) [FEEDBACK_USED]</violation>
</file>

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

Comment thread apps/desktop/src/main/lib/local-db/index.ts Outdated
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.

🧹 Nitpick comments (1)
apps/desktop/src/main/lib/local-db/index.ts (1)

109-141: Consider using Drizzle ORM instead of raw SQLite statements.

The coding guidelines specify "Use Drizzle ORM for all database operations", but this repair logic uses raw sqlite.prepare() calls. Since localDb is already initialized at this point, the queries could be refactored to use Drizzle for consistency.

Additionally, the summary log at line 146 reports corruptedWorktrees.length, which is the number of worktrees queried, not the number actually repaired (some may have unreadable HEAD files). Consider tracking successful repairs separately.

♻️ Suggested refactor using Drizzle ORM
+import { eq } from "drizzle-orm";
 // ... at the repair section:

-	const corruptedWorktrees = sqlite
-		.prepare(
-			"SELECT id, path FROM worktrees WHERE branch = '[gone]'",
-		)
-		.all() as { id: string; path: string }[];
+	const corruptedWorktrees = localDb
+		.select({ id: schema.worktrees.id, path: schema.worktrees.path })
+		.from(schema.worktrees)
+		.where(eq(schema.worktrees.branch, "[gone]"))
+		.all();

+	let repairedCount = 0;
 	for (const wt of corruptedWorktrees) {
 		// ... branch resolution logic unchanged ...

 		if (realBranch) {
-			sqlite
-				.prepare("UPDATE worktrees SET branch = ? WHERE id = ?")
-				.run(realBranch, wt.id);
-			sqlite
-				.prepare(
-					"UPDATE workspaces SET branch = ? WHERE worktree_id = ? AND branch = '[gone]'",
-				)
-				.run(realBranch, wt.id);
+			localDb
+				.update(schema.worktrees)
+				.set({ branch: realBranch })
+				.where(eq(schema.worktrees.id, wt.id))
+				.run();
+			localDb
+				.update(schema.workspaces)
+				.set({ branch: realBranch })
+				.where(
+					and(
+						eq(schema.workspaces.worktreeId, wt.id),
+						eq(schema.workspaces.branch, "[gone]"),
+					),
+				)
+				.run();
+			repairedCount++;
 			console.log(
 				`[local-db] Repaired branch for worktree ${wt.id}: [gone] → ${realBranch}`,
 			);
 		}
 	}

-	if (corruptedWorktrees.length > 0) {
+	if (repairedCount > 0) {
 		console.log(
-			`[local-db] Repaired ${corruptedWorktrees.length} corrupted worktree branch(es)`,
+			`[local-db] Repaired ${repairedCount} corrupted worktree branch(es)`,
 		);
 	}

As per coding guidelines: "Use Drizzle ORM for all database operations".

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

In `@apps/desktop/src/main/lib/local-db/index.ts` around lines 109 - 141, The code
uses raw sqlite.prepare() calls (corruptedWorktrees query and subsequent
UPDATEs) instead of the project's Drizzle ORM and also logs
corruptedWorktrees.length rather than the number actually repaired; replace the
raw SQL in this repair block by using the initialized Drizzle client (localDb /
drizzle instance) to query worktrees (instead of sqlite.prepare("SELECT ...")),
and perform the two updates via Drizzle update calls targeting the worktrees and
workspaces tables (the UPDATEs currently run after finding realBranch), while
incrementing a repaired counter when an update runs successfully; finally,
change the summary log to report the repaired count (not
corruptedWorktrees.length).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/main/lib/local-db/index.ts`:
- Around line 109-141: The code uses raw sqlite.prepare() calls
(corruptedWorktrees query and subsequent UPDATEs) instead of the project's
Drizzle ORM and also logs corruptedWorktrees.length rather than the number
actually repaired; replace the raw SQL in this repair block by using the
initialized Drizzle client (localDb / drizzle instance) to query worktrees
(instead of sqlite.prepare("SELECT ...")), and perform the two updates via
Drizzle update calls targeting the worktrees and workspaces tables (the UPDATEs
currently run after finding realBranch), while incrementing a repaired counter
when an update runs successfully; finally, change the summary log to report the
repaired count (not corruptedWorktrees.length).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 31596822-7761-4a99-9ab0-828b021bf297

📥 Commits

Reviewing files that changed from the base of the PR and between 115c475 and 4a61d95.

📒 Files selected for processing (3)
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
  • apps/desktop/src/main/lib/local-db/index.ts

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 26, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Fly.io Electric (Fly.io) View App
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

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 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="apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts">

<violation number="1" location="apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts:296">
P2: `u` records are always unmerged/conflicted in porcelain v2, but the parser only flags conflicts when XY contains `U`. Conflict types like `AA`/`DD` won’t be reported. Mark all `u` entries as conflicted when parsing.</violation>
</file>

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

Comment thread apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
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)
packages/host-service/src/runtime/pull-requests/pull-requests.ts (1)

184-202: ⚠️ Potential issue | 🟠 Major

Separate branch resolution from HEAD hash lookup to repair branches on unborn worktrees.

Line 186 blocks the update on unborn branches: Promise.all() rejects when git.revparse(["HEAD"]) fails, preventing the branch name (which getCurrentBranchName() can resolve via symbolic-ref) from being persisted. When getCurrentBranchName() returns null, the if (!branch) { continue; } also leaves stale PR linkage unchanged indefinitely. Resolve the branch first, then attempt headSha with error handling, or explicitly clear PR state when no branch can be resolved.

💡 Suggested direction
-				const [branch, rawHeadSha] = await Promise.all([
-					getCurrentBranchName(git),
-					git.revparse(["HEAD"]),
-				]);
-				if (!branch) {
-					continue;
-				}
-				const headSha = rawHeadSha.trim();
+				const branch = await getCurrentBranchName(git);
+				const headSha = await git
+					.revparse(["HEAD"])
+					.then((value) => value.trim())
+					.catch(() => null);
+
+				if (!branch) {
+					// Clear any stale PR linkage here if detached/unresolvable
+					// workspaces should not retain their previous branch state.
+					continue;
+				}
@@
-					.set({
-						branch,
-						headSha,
-					})
+					.set({
+						branch,
+						...(headSha ? { headSha } : {}),
+					})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/host-service/src/runtime/pull-requests/pull-requests.ts` around
lines 184 - 202, The Promise.all() call mixes branch resolution and rev-parse so
a failing git.revparse(["HEAD"]) aborts persisting a resolvable branch; change
the logic in the loop to call getCurrentBranchName(git) first and handle its
result (if branch is null either clear PR linkage by updating branch/headSha to
null via this.db.update(workspaces).set(...) or continue after deciding clear vs
skip), then separately attempt to get headSha by calling git.revparse(["HEAD"])
inside a try/catch and set headSha to trimmed value on success or to
null/undefined on failure before comparing to workspace.branch/workspace.headSha
and performing the update; reference getCurrentBranchName, git.revparse,
headSha, workspace.branch, workspace.headSha, and
this.db.update(workspaces).set(...) when making the change.
🤖 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 `@packages/host-service/src/runtime/pull-requests/pull-requests.ts`:
- Around line 184-202: The Promise.all() call mixes branch resolution and
rev-parse so a failing git.revparse(["HEAD"]) aborts persisting a resolvable
branch; change the logic in the loop to call getCurrentBranchName(git) first and
handle its result (if branch is null either clear PR linkage by updating
branch/headSha to null via this.db.update(workspaces).set(...) or continue after
deciding clear vs skip), then separately attempt to get headSha by calling
git.revparse(["HEAD"]) inside a try/catch and set headSha to trimmed value on
success or to null/undefined on failure before comparing to
workspace.branch/workspace.headSha and performing the update; reference
getCurrentBranchName, git.revparse, headSha, workspace.branch,
workspace.headSha, and this.db.update(workspaces).set(...) when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 91083718-d52e-424e-9c1a-eac2d6861b86

📥 Commits

Reviewing files that changed from the base of the PR and between 4a61d95 and dc3957a.

📒 Files selected for processing (8)
  • apps/desktop/src/lib/trpc/routers/changes/git-operations.ts
  • apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.test.ts
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.test.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • packages/host-service/src/runtime/pull-requests/pull-requests.ts

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.

🧹 Nitpick comments (3)
packages/host-service/test/pull-requests.test.ts (1)

1-3: Move this test next to pull-requests.ts to match repo test co-location rules.

Please place this as a co-located test (for example, alongside packages/host-service/src/runtime/pull-requests/pull-requests.ts) instead of the shared test/ folder.

As per coding guidelines, **/*.{ts,tsx}: Co-locate tests with their implementation files (e.g., Component.test.tsx next to Component.tsx).

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

In `@packages/host-service/test/pull-requests.test.ts` around lines 1 - 3, Move
the test file from the shared test folder into the same directory as the
implementation file: place packages/host-service/test/pull-requests.test.ts next
to the implementation pull-requests.ts (e.g.,
packages/host-service/src/runtime/pull-requests/pull-requests.test.ts); update
the import path for PullRequestRuntimeManager if needed to a relative import
that points to pull-requests.ts and ensure the test still imports
describe/expect/mock/test from "bun:test".
packages/host-service/src/runtime/pull-requests/pull-requests.ts (1)

195-197: Consider lightweight debug logging when branch resolution is skipped.

Line 195 silently continues on unresolved branch; a debug-level log here would improve diagnosis of recurring workspace sync gaps without adding noisy warnings.

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

In `@packages/host-service/src/runtime/pull-requests/pull-requests.ts` around
lines 195 - 197, When branch resolution yields falsy and the code hits "if
(!branch) { continue; }", add a lightweight debug log before the continue so
unresolved branches are recorded for diagnostics; use the module's existing
logger (e.g., logger.debug or processLogger.debug) to emit a concise message
including the PR identifier or workspace id and the resolved branch value
(null/undefined) so it remains silent in normal runs but helps trace recurring
sync gaps.
apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts (1)

308-327: Consider adding defensive handling for missing rename source path.

For type 2 (rename/copy) records, the original path is expected on the next NUL-separated entry. If from is undefined (e.g., malformed output), the code falls back to using path as the original, which could mask parsing issues.

The current fallback at line 315 is reasonable, but consider logging a warning when the expected from entry is missing to aid debugging.

💡 Optional: Add warning for missing rename source
 if (entry.startsWith("2 ")) {
   const match = entry.match(/^2 (\S{2}) \S+ \S+ \S+ \S+ \S+ \S+ \S+ (.+)$/);
   const from = entries[i + 1];
   if (match) {
     const xy = match[1] || "..";
     const path = match[2];
     if (path) {
-      const originalPath = from || path;
+      const originalPath = from || path;
+      if (!from) {
+        console.warn("[parsePorcelainStatusV2] Missing original path for rename entry:", path);
+      }
       renamed.push({ from: originalPath, to: path });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts` around lines 308 -
327, The rename/copy handling block (when entry.startsWith("2 ")) should
defensively detect when the expected source entry `from = entries[i + 1]` is
missing and emit a warning before falling back to using `path` as the original;
update the block around variables `entry`, `from`, `match`, `path`, `renamed`,
and the call to `addFile`/`normalizeStatusCode` to: if `from` is undefined log a
concise warning (using the project's logger instance, e.g., logger.warn or
processLogger.warn) including the raw `entry` (or `match[0]`) and the computed
`path`/index `i`, then proceed with the existing fallback logic that pushes to
`renamed` and calls `addFile`. Ensure the warning contains enough context (entry
type "2", xy, path, and i) and does not change the current control flow (still
increment `i += 2` and continue).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts`:
- Around line 308-327: The rename/copy handling block (when entry.startsWith("2
")) should defensively detect when the expected source entry `from = entries[i +
1]` is missing and emit a warning before falling back to using `path` as the
original; update the block around variables `entry`, `from`, `match`, `path`,
`renamed`, and the call to `addFile`/`normalizeStatusCode` to: if `from` is
undefined log a concise warning (using the project's logger instance, e.g.,
logger.warn or processLogger.warn) including the raw `entry` (or `match[0]`) and
the computed `path`/index `i`, then proceed with the existing fallback logic
that pushes to `renamed` and calls `addFile`. Ensure the warning contains enough
context (entry type "2", xy, path, and i) and does not change the current
control flow (still increment `i += 2` and continue).

In `@packages/host-service/src/runtime/pull-requests/pull-requests.ts`:
- Around line 195-197: When branch resolution yields falsy and the code hits "if
(!branch) { continue; }", add a lightweight debug log before the continue so
unresolved branches are recorded for diagnostics; use the module's existing
logger (e.g., logger.debug or processLogger.debug) to emit a concise message
including the PR identifier or workspace id and the resolved branch value
(null/undefined) so it remains silent in normal runs but helps trace recurring
sync gaps.

In `@packages/host-service/test/pull-requests.test.ts`:
- Around line 1-3: Move the test file from the shared test folder into the same
directory as the implementation file: place
packages/host-service/test/pull-requests.test.ts next to the implementation
pull-requests.ts (e.g.,
packages/host-service/src/runtime/pull-requests/pull-requests.test.ts); update
the import path for PullRequestRuntimeManager if needed to a relative import
that points to pull-requests.ts and ensure the test still imports
describe/expect/mock/test from "bun:test".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58a7c77b-ba7d-44ad-a0cd-642bfbbc924c

📥 Commits

Reviewing files that changed from the base of the PR and between dc3957a and 3e6cdbe.

📒 Files selected for processing (7)
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.test.ts
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.test.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • packages/host-service/src/runtime/pull-requests/pull-requests.ts
  • packages/host-service/test/pull-requests.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.test.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.

2 issues found across 7 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/runtime/pull-requests/pull-requests.ts">

<violation number="1" location="packages/host-service/src/runtime/pull-requests/pull-requests.ts:48">
P2: Avoid swallowing all `revparse("HEAD")` errors here. Only suppress the expected unborn-HEAD case; rethrow unexpected errors so they are observable and don’t silently null out `headSha`.

(Based on your team's feedback about avoiding empty catch blocks that hide failures.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts">

<violation number="1" location="apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts:47">
P2: This catch masks all `rev-parse HEAD` errors and always downgrades to `headSha = undefined`. Only the known unborn-HEAD failure should be tolerated; other errors should be rethrown/logged so PR resolution doesn’t silently lose SHA disambiguation.

(Based on your team's feedback about avoiding silent catch blocks.) [FEEDBACK_USED]</violation>
</file>

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

Comment thread packages/host-service/src/runtime/pull-requests/pull-requests.ts Outdated
Comment thread apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts Outdated
@Kitenite Kitenite merged commit d9a8206 into main Mar 26, 2026
6 of 7 checks passed
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

🧹 Nitpick comments (2)
apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts (1)

314-329: Consider adding a fallback for malformed ordinary change records.

The regex for ordinary change records (1 XY ...) assumes a specific format. If the regex doesn't match, the entry is silently skipped.

While unlikely in practice, if git emits a slightly different format in future versions, files could be silently missed. The current behavior (skip and continue) is defensive, but you may want to add debug logging for unmatched entries during development.

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

In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts` around lines 314 -
329, The ordinary change record branch that handles entries starting with "1 "
currently ignores malformed lines when the regex in that block doesn't match;
update the branch in the same block (where entry is checked with
entry.startsWith("1 ") and match is derived) to add a fallback: when match is
falsy, emit a debug/processLogger.warn including the raw entry string so
malformed records are visible during development, and optionally attempt a
conservative parse (e.g., split on spaces and take the last token as path)
before continuing; keep existing use of addFile and normalizeStatusCode for
properly parsed records.
packages/host-service/test/pull-requests.test.ts (1)

1-2: Consider co-locating this test with pull-requests.ts.

Placing this test next to packages/host-service/src/runtime/pull-requests/pull-requests.ts would align with the repository’s test-location rule and reduce cross-folder coupling.

As per coding guidelines, "Co-locate tests with their implementation files (e.g., Component.test.tsx next to Component.tsx)."

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

In `@packages/host-service/test/pull-requests.test.ts` around lines 1 - 2, The
test file pull-requests.test.ts should be moved to sit next to the
implementation pull-requests.ts and its imports updated accordingly: relocate
packages/host-service/test/pull-requests.test.ts to the same directory as
pull-requests.ts, update the import of PullRequestRuntimeManager to use a
relative path (pointing to "./pull-requests" or the local module name), and
adjust any test-runner or export references if necessary so the test imports
PullRequestRuntimeManager from the co-located module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/host-service/test/pull-requests.test.ts`:
- Around line 115-124: The test creates a Jest spy with const warnSpy =
spyOn(console, "warn") but never restores it, which can leak into other tests;
fix by restoring the spy after use (e.g., call warnSpy.mockRestore() after the
assertions in this test) or add an afterEach cleanup that calls
warnSpy.mockRestore() when defined. Locate the warnSpy declaration in the test
that calls (manager as unknown as { syncWorkspaceBranches: () => Promise<void>
}).syncWorkspaceBranches() and ensure you call warnSpy.mockRestore() (or use
jest.restoreAllMocks() in afterEach) so console.warn is returned to its original
implementation.

---

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts`:
- Around line 314-329: The ordinary change record branch that handles entries
starting with "1 " currently ignores malformed lines when the regex in that
block doesn't match; update the branch in the same block (where entry is checked
with entry.startsWith("1 ") and match is derived) to add a fallback: when match
is falsy, emit a debug/processLogger.warn including the raw entry string so
malformed records are visible during development, and optionally attempt a
conservative parse (e.g., split on spaces and take the last token as path)
before continuing; keep existing use of addFile and normalizeStatusCode for
properly parsed records.

In `@packages/host-service/test/pull-requests.test.ts`:
- Around line 1-2: The test file pull-requests.test.ts should be moved to sit
next to the implementation pull-requests.ts and its imports updated accordingly:
relocate packages/host-service/test/pull-requests.test.ts to the same directory
as pull-requests.ts, update the import of PullRequestRuntimeManager to use a
relative path (pointing to "./pull-requests" or the local module name), and
adjust any test-runner or export references if necessary so the test imports
PullRequestRuntimeManager from the co-located module.
🪄 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: 07dee775-c931-4af0-8e3b-880657c9a6a2

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6cdbe and bd08307.

📒 Files selected for processing (7)
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.test.ts
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.test.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/github/github.ts
  • packages/host-service/src/runtime/pull-requests/pull-requests.ts
  • packages/host-service/test/pull-requests.test.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/lib/trpc/routers/changes/utils/merge-pull-request.ts
  • packages/host-service/src/runtime/pull-requests/pull-requests.ts

Comment on lines +115 to +124
const warnSpy = spyOn(console, "warn").mockImplementation(() => {});

await (
manager as unknown as { syncWorkspaceBranches: () => Promise<void> }
).syncWorkspaceBranches();

expect(setMock).not.toHaveBeenCalled();
expect(refreshProjectMock).not.toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalled();
});
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify this file has a console.warn spy without restoration.
rg -n -C2 'spyOn\(console,\s*"warn"\)|mockRestore\(' packages/host-service/test/pull-requests.test.ts

Repository: superset-sh/superset

Length of output: 277


🏁 Script executed:

#!/bin/bash
# Get full context of the test around lines 115-124, plus any cleanup hooks
sed -n '100,150p' packages/host-service/test/pull-requests.test.ts

Repository: superset-sh/superset

Length of output: 838


🏁 Script executed:

#!/bin/bash
# Search for afterEach or afterAll cleanup hooks in the entire test file
rg -n 'afterEach|afterAll' packages/host-service/test/pull-requests.test.ts

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Find the test function definition that includes line 115
sed -n '75,125p' packages/host-service/test/pull-requests.test.ts

Repository: superset-sh/superset

Length of output: 1515


🏁 Script executed:

#!/bin/bash
# Search for other spyOn calls in the file to see the pattern
rg -n 'spyOn\(' packages/host-service/test/pull-requests.test.ts

Repository: superset-sh/superset

Length of output: 138


🏁 Script executed:

#!/bin/bash
# Find the test function definition
sed -n '1,80p' packages/host-service/test/pull-requests.test.ts | tail -30

Repository: superset-sh/superset

Length of output: 928


🏁 Script executed:

#!/bin/bash
# Check imports and test framework
head -20 packages/host-service/test/pull-requests.test.ts

Repository: superset-sh/superset

Length of output: 807


🏁 Script executed:

#!/bin/bash
# Count number of tests in file
rg -c "it\(|test\(" packages/host-service/test/pull-requests.test.ts

Repository: superset-sh/superset

Length of output: 65


🏁 Script executed:

#!/bin/bash
# Check Bun test framework documentation pattern by searching for mockRestore in codebase
rg -n 'mockRestore' packages/host-service/test/ --type ts --type tsx

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Check if there are other test files in the project and how they handle spyOn
find packages/host-service/test -name "*.test.ts" -o -name "*.test.tsx" | head -5

Repository: superset-sh/superset

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Check other test files for spyOn patterns
rg -l 'spyOn' packages/ --type ts --type tsx | head -5

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Search for mockRestore usage in entire codebase
rg -n 'mockRestore' packages/ --type ts

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

#!/antml:bin/bash
# Find other test files with spyOn to see patterns
rg -l 'spyOn' packages/ --type ts

Repository: superset-sh/superset

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Check Bun test docs about spy cleanup - search for test cleanup patterns in codebase
rg -n 'afterEach|afterAll|finally.*mockRestore' packages/ --type ts

Repository: superset-sh/superset

Length of output: 2305


🏁 Script executed:

#!/bin/bash
# Check what typical afterEach cleanup does in similar test files
sed -n '19,25p' packages/workspace-fs/src/host/service.test.ts

Repository: superset-sh/superset

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Check the pull-requests.test.ts file structure - does it have afterEach at all?
rg -n 'describe|afterEach|beforeEach' packages/host-service/test/pull-requests.test.ts

Repository: superset-sh/superset

Length of output: 189


Restore console.warn spy to avoid cross-test leakage.

warnSpy is never restored, which can affect the other test in this file. The codebase uses afterEach cleanup patterns elsewhere.

Proposed fix
 		const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
-
-		await (
-			manager as unknown as { syncWorkspaceBranches: () => Promise<void> }
-		).syncWorkspaceBranches();
-
-		expect(setMock).not.toHaveBeenCalled();
-		expect(refreshProjectMock).not.toHaveBeenCalled();
-		expect(warnSpy).toHaveBeenCalled();
+		try {
+			await (
+				manager as unknown as { syncWorkspaceBranches: () => Promise<void> }
+			).syncWorkspaceBranches();
+
+			expect(setMock).not.toHaveBeenCalled();
+			expect(refreshProjectMock).not.toHaveBeenCalled();
+			expect(warnSpy).toHaveBeenCalled();
+		} finally {
+			warnSpy.mockRestore();
+		}
📝 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
const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
await (
manager as unknown as { syncWorkspaceBranches: () => Promise<void> }
).syncWorkspaceBranches();
expect(setMock).not.toHaveBeenCalled();
expect(refreshProjectMock).not.toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalled();
});
const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
try {
await (
manager as unknown as { syncWorkspaceBranches: () => Promise<void> }
).syncWorkspaceBranches();
expect(setMock).not.toHaveBeenCalled();
expect(refreshProjectMock).not.toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalled();
} finally {
warnSpy.mockRestore();
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/host-service/test/pull-requests.test.ts` around lines 115 - 124, The
test creates a Jest spy with const warnSpy = spyOn(console, "warn") but never
restores it, which can leak into other tests; fix by restoring the spy after use
(e.g., call warnSpy.mockRestore() after the assertions in this test) or add an
afterEach cleanup that calls warnSpy.mockRestore() when defined. Locate the
warnSpy declaration in the test that calls (manager as unknown as {
syncWorkspaceBranches: () => Promise<void> }).syncWorkspaceBranches() and ensure
you call warnSpy.mockRestore() (or use jest.restoreAllMocks() in afterEach) so
console.warn is returned to its original implementation.

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