Skip to content

fix: fall back to FETCH_HEAD when gh pr checkout fails for branch names with /#3232

Merged
Kitenite merged 3 commits intosuperset-sh:mainfrom
ruangustavo:fix/pr-checkout-slash-branch-worktree
Apr 19, 2026
Merged

fix: fall back to FETCH_HEAD when gh pr checkout fails for branch names with /#3232
Kitenite merged 3 commits intosuperset-sh:mainfrom
ruangustavo:fix/pr-checkout-slash-branch-worktree

Conversation

@ruangustavo
Copy link
Copy Markdown
Contributor

@ruangustavo ruangustavo commented Apr 7, 2026

Closes #3231

What

When creating a workspace linked to a PR whose branch name contains / (e.g. user/feature-branch), gh pr checkout internally runs git checkout -b <branch> --track origin/<branch>. Inside a freshly created detached worktree, git cannot resolve the remote tracking ref when the branch name has a /, producing:

fatal: cannot set up tracking information; starting point 'origin/user/feature-branch' is not a branch

The fetch itself succeeds — only the --track setup fails.

Fix

Wrap the gh pr checkout call in a try/catch. When the error contains "is not a branch", fall back to:

git checkout -b <localBranchName> --no-track FETCH_HEAD

Since gh pr checkout already fetched the remote before failing, FETCH_HEAD points to the correct commit. push.autoSetupRemote = true (already configured after worktree creation) handles push tracking without needing --track.


Summary by cubic

Fixes PR checkout when branch names contain "/" by falling back to a local FETCH_HEAD checkout if gh pr checkout fails. Restores workspace creation and logs the fallback path for easier debugging (fixes #3231).

  • Bug Fixes
    • Try/catch gh pr checkout; on "is not a branch", run git checkout -B <localBranchName> --no-track FETCH_HEAD to create/replace the local branch.
    • Reuses the fetch done by gh; FETCH_HEAD points to the right commit.
    • Logs a clear message when using the fallback to aid troubleshooting.
    • Keeps push.autoSetupRemote=true so git push sets upstream without --track.

Written for commit 919665d. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved robustness of branch checkout in workspace management: when the primary checkout method fails with a specific error, the system falls back to an alternative checkout to ensure the branch is created reliably, reducing failed branch setup scenarios and improving developer workflow continuity.

…ranch names with /

Fixes superset-sh#3231

gh pr checkout internally runs `git checkout -b <branch> --track origin/<branch>`.
When the branch name contains `/`, git cannot resolve the tracking ref inside a
freshly created detached worktree, producing "starting point is not a branch".

The fetch succeeds — only the tracking setup fails. Catch that specific error and
fall back to `git checkout -b <localBranchName> --no-track FETCH_HEAD`.
push.autoSetupRemote=true (already set after worktree creation) handles push
tracking without needing --track.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

Wraps the gh pr checkout invocation in a try/catch; if the error message contains "is not a branch", the code falls back to git checkout -B <localBranchName> --no-track FETCH_HEAD (shorter timeout) to create the local branch. Other errors are rethrown; surrounding worktree flow unchanged.

Changes

Cohort / File(s) Summary
Git Worktree PR Checkout Error Handling
apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
Added try/catch around gh pr checkout. If error message includes "is not a branch", log fallback and run git checkout -B <localBranchName> --no-track FETCH_HEAD with a shorter timeout; rethrow other errors. No changes to worktree creation or push.autoSetupRemote logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

Poem

🐰 A slash once tripped up a branch in the glen,
The GH checkout faltered and said "not a branch" then.
I bounced to FETCH_HEAD with a brave little hop,
Created the branch, and hopped back on top! 🌿✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a fallback mechanism to use FETCH_HEAD when gh pr checkout fails for branch names containing forward slashes.
Description check ✅ Passed The PR description provides a comprehensive overview with clear problem statement, solution explanation, and implementation details. It addresses the issue number and includes expected sections despite not following the exact template structure.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 7, 2026

Greptile Summary

This PR fixes a crash when creating a worktree linked to a PR whose branch name contains / (e.g. user/feature-branch). The root cause is gh pr checkout internally attempting git checkout -b <branch> --track origin/<branch> inside a freshly created detached worktree — git cannot resolve origin/user/feature-branch as a tracking ref when the branch name contains slashes, producing fatal: starting point 'origin/user/feature-branch' is not a branch. The fetch itself succeeds; only the --track setup fails.

The fix wraps the gh pr checkout call in a try/catch and, when the specific "is not a branch" substring is detected in the error message, falls back to git checkout -b <localBranchName> --no-track FETCH_HEAD — since gh already populated FETCH_HEAD before failing. push.autoSetupRemote = true (already configured in the happy path) handles push tracking without needing --track.

  • Fallback checkout: On "is not a branch" error, creates branch from FETCH_HEAD with --no-track
  • Error detection: ghMsg.includes("is not a branch") correctly matches git's stderr (confirmed that execWithShellEnv includes stderr in the thrown error's .message)
  • push.autoSetupRemote: Already set after worktree creation, so git push still works without explicit tracking
  • Minor edge case: When branchExists is true the fallback git checkout -b may fail because the branch is already checked out

Confidence Score: 4/5

Safe to merge — fixes a real, reproducible bug for the common case with one minor edge-case that is unlikely in practice

The fix correctly handles the primary failure scenario (detached worktree + branch name containing /). The FETCH_HEAD assumption is well-founded: gh pr checkout fetches successfully before failing on the --track step, so FETCH_HEAD reliably points to the PR head. A minor edge-case exists where branchExists=true and gh still emits the same error — in that case git checkout -b would fail because the branch already exists. This is unlikely in practice and results in a clear error message rather than silent data corruption.

apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts — specifically the fallback path in createWorktreeFromPr when branchExists is true

Important Files Changed

Filename Overview
apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts Adds try/catch around gh pr checkout with a FETCH_HEAD fallback for branch names containing /; minor edge-case when branch already exists locally

Sequence Diagram

sequenceDiagram
    participant C as createWorktreeFromPr
    participant W as git worktree
    participant GH as gh CLI
    participant G as git

    C->>W: git worktree add (detached or with branch)
    C->>GH: gh pr checkout number --branch localBranchName --force
    alt success
        GH-->>C: ok (branch + tracking configured)
    else is not a branch error
        GH-->>C: fatal: starting point origin/user/feature is not a branch
        Note over GH,G: gh fetched remote before failing, FETCH_HEAD is valid
        C->>G: git checkout -b localBranchName --no-track FETCH_HEAD
        G-->>C: ok (branch created, no upstream tracking)
    else other error
        GH-->>C: re-throw original error
    end
    C->>G: git config push.autoSetupRemote true
    Note over C,G: autoSetupRemote handles push tracking without --track
Loading

Reviews (1): Last reviewed commit: "fix: fall back to FETCH_HEAD checkout wh..." | Re-trigger Greptile

Comment thread apps/desktop/src/lib/trpc/routers/workspaces/utils/git.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.

1 issue found across 1 file

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:1808">
P2: When the branch already exists locally (`branchExists === true`), this fallback will fail with `"A branch named '<localBranchName>' already exists"` because `checkout -b` requires the branch to not exist. That error won't be caught by the outer error handlers, surfacing as a generic failure.

Consider branching on `branchExists`: when true, use `git reset --hard FETCH_HEAD` (the branch is already checked out in the worktree); when false, use the current `checkout -b` path.</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 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.

Actionable comments posted: 1

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

1793-1802: Consider logging when fallback path is taken.

When the fallback is triggered, there's no indication in the logs. Adding a log statement would help with debugging and understanding which code path was executed.

Suggested improvement
 		} catch (ghError) {
 			const ghMsg = ghError instanceof Error ? ghError.message : String(ghError);
 			// `gh pr checkout` can fail with "is not a branch" when the branch name
 			// contains '/' (e.g. "user/feature-branch"). Git has trouble resolving
 			// "origin/user/feature-branch" as a tracking ref inside a worktree.
 			// gh already fetched the remote successfully, so FETCH_HEAD points to
 			// the right commit — fall back to creating the branch without tracking.
 			if (!ghMsg.includes("is not a branch")) {
 				throw ghError;
 			}
+			console.log(
+				`[git] gh pr checkout failed with tracking error for PR #${prInfo.number}, falling back to FETCH_HEAD checkout`,
+			);
 			await execGitWithShellPath(
🤖 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 1793
- 1802, In the catch block that handles ghError (where ghMsg is derived) add a
log statement when the fallback branch-creation path is taken (i.e., when
ghMsg.includes("is not a branch")); log the fact that the "is not a branch"
fallback is being used along with the branch name/PR identifier and ghMsg (and
include ghError stack if available) using the project logger (e.g.,
processLogger.warn or similar) so it's easy to trace when the non-tracking
branch creation is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts`:
- Around line 1803-1814: The fallback git checkout call inside
createWorktreeFromPr can fail when branchExists is true because the worktree may
already have the branch locally; update the execGitWithShellPath invocation that
passes ["-C", worktreePath, "checkout", "-b", localBranchName, "--no-track",
"FETCH_HEAD"] to use a force-replace flag (use "-B" instead of "-b") so the
checkout will succeed whether the branch already exists or not; locate the call
to execGitWithShellPath and replace the "-b" token with "-B" while keeping
localBranchName, "--no-track", and "FETCH_HEAD" intact.

---

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts`:
- Around line 1793-1802: In the catch block that handles ghError (where ghMsg is
derived) add a log statement when the fallback branch-creation path is taken
(i.e., when ghMsg.includes("is not a branch")); log the fact that the "is not a
branch" fallback is being used along with the branch name/PR identifier and
ghMsg (and include ghError stack if available) using the project logger (e.g.,
processLogger.warn or similar) so it's easy to trace when the non-tracking
branch creation is used.
🪄 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: 49546284-912e-47f9-aaf2-24d96a606a1d

📥 Commits

Reviewing files that changed from the base of the PR and between 97031ad and 830d4e9.

📒 Files selected for processing (1)
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts

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.

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

1780-1818: LGTM! Well-structured fallback for the / branch name issue.

The try/catch approach with specific error detection is a pragmatic solution. The comment clearly documents the rationale, and logging the fallback path aids debugging. The -B flag correctly handles both existing and non-existing branch scenarios.

One optional hardening consideration: the error string check is case-sensitive. While git error messages are typically lowercase, a case-insensitive check would be slightly more defensive:

Optional: case-insensitive error matching
-			if (!ghMsg.includes("is not a branch")) {
+			if (!ghMsg.toLowerCase().includes("is not a branch")) {
🤖 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 1780
- 1818, Modify the error-message check to be case-insensitive by normalizing
ghMsg before matching: when catching ghError in the gh pr checkout block
(variables ghError/ghMsg, function execWithShellEnv), convert ghMsg to a single
case (e.g., lowerCase) and test for the substring "is not a branch" against that
normalized string; keep the same fallback path that calls execGitWithShellPath
to checkout FETCH_HEAD into localBranchName and rethrow other errors unchanged
(use prInfo.number in the log as currently done).
🤖 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 1780-1818: Modify the error-message check to be case-insensitive
by normalizing ghMsg before matching: when catching ghError in the gh pr
checkout block (variables ghError/ghMsg, function execWithShellEnv), convert
ghMsg to a single case (e.g., lowerCase) and test for the substring "is not a
branch" against that normalized string; keep the same fallback path that calls
execGitWithShellPath to checkout FETCH_HEAD into localBranchName and rethrow
other errors unchanged (use prInfo.number in the log as currently done).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4b53e48-3498-46d4-ba60-a311f34233cd

📥 Commits

Reviewing files that changed from the base of the PR and between 830d4e9 and 919665d.

📒 Files selected for processing (1)
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts

@Kitenite Kitenite merged commit 27e243b into superset-sh:main Apr 19, 2026
6 of 7 checks passed
Kitenite added a commit that referenced this pull request Apr 20, 2026
- Fix Biome formatting in git.ts (PR #3232 lint regression)
- Update NotificationManager test to match renamed notification strings
  ('Awaiting Response' / 'is waiting for your reply')
- Add DATABASE_URL and DATABASE_URL_UNPOOLED to marketing deploy
  workflows (production + preview) — the neon() client init crashes
  at module evaluation time when the env var is missing, which broke
  the marketing build since Apr 19

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai>
Kitenite added a commit that referenced this pull request Apr 20, 2026
- Fix Biome formatting in git.ts (PR #3232 lint regression)
- Update NotificationManager test to match renamed notification strings
  ('Awaiting Response' / 'is waiting for your reply')
- Convert CTAButtons auth import to dynamic import — the top-level
  import of @superset/auth/server triggered neon() at module evaluation
  time during Next.js static page collection, crashing the marketing
  build when DATABASE_URL was absent. Dynamic import defers DB init
  to runtime inside a try-catch, so it fails gracefully.

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai>
Kitenite added a commit that referenced this pull request Apr 20, 2026
- Fix Biome formatting in git.ts (PR #3232 lint regression)
- Update NotificationManager test to match renamed notification strings
  ('Awaiting Response' / 'is waiting for your reply')
- Convert CTAButtons auth import to dynamic import to prevent neon()
  from crashing during Next.js static page collection at build time
- Add DATABASE_URL to marketing deploy workflows (production + preview)
  so the session check works at runtime and logged-in users see the
  Dashboard link

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai>
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.

gh pr checkout fails for PR branch names containing / inside a git worktree

2 participants