From 25fd93d7e2f1443c1c0f918204cdd343e1804d63 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 14 Apr 2026 20:49:54 +0700 Subject: [PATCH] fix: handle repos with no commits in getCurrentBranch git rev-parse --abbrev-ref HEAD fails with exit code 128 on freshly initialized repos with no commits. This error bubbled up through workspace listing and showed a toast error on every app launch for users with empty git repos. --- packages/server/src/server/worktree-session.ts | 11 +++-------- packages/server/src/utils/checkout-git.test.ts | 10 ++++++++++ packages/server/src/utils/checkout-git.ts | 16 ++++++++++------ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/server/src/server/worktree-session.ts b/packages/server/src/server/worktree-session.ts index eae15c417..e45b507e9 100644 --- a/packages/server/src/server/worktree-session.ts +++ b/packages/server/src/server/worktree-session.ts @@ -21,7 +21,7 @@ import type { WorkspaceGitService } from "./workspace-git-service.js"; import { normalizeWorkspaceId as normalizePersistedWorkspaceId } from "./workspace-registry-model.js"; import { createAgentWorktree } from "./worktree-bootstrap.js"; import type { TerminalManager } from "../terminal/terminal-manager.js"; -import { resolveRepositoryDefaultBranch } from "../utils/checkout-git.js"; +import { getCurrentBranch, resolveRepositoryDefaultBranch } from "../utils/checkout-git.js"; import { expandTilde } from "../utils/path.js"; import { computeWorktreePath, @@ -35,8 +35,7 @@ import { validateBranchSlug, type WorktreeConfig, } from "../utils/worktree.js"; -import { runGitCommand } from "../utils/run-git-command.js"; -import { READ_ONLY_GIT_ENV, toCheckoutError } from "./checkout-git-utils.js"; +import { toCheckoutError } from "./checkout-git-utils.js"; const SAFE_GIT_REF_PATTERN = /^[A-Za-z0-9._\/-]+$/; export type NormalizedGitOptions = { @@ -153,11 +152,7 @@ export async function buildAgentSessionConfig( if (normalized.createNewBranch) { targetBranch = normalized.newBranchName!; } else { - const { stdout } = await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { - cwd, - env: READ_ONLY_GIT_ENV, - }); - targetBranch = stdout.trim(); + targetBranch = (await getCurrentBranch(cwd)) ?? ""; } if (!targetBranch) { diff --git a/packages/server/src/utils/checkout-git.test.ts b/packages/server/src/utils/checkout-git.test.ts index 3ce01f7cc..5f4f5ba8a 100644 --- a/packages/server/src/utils/checkout-git.test.ts +++ b/packages/server/src/utils/checkout-git.test.ts @@ -18,6 +18,7 @@ import { __setGhPathForTests, __setPullRequestStatusCacheTtlForTests, commitAll, + getCurrentBranch, getCheckoutDiff, getCheckoutShortstat, getPullRequestStatus, @@ -84,6 +85,15 @@ describe("checkout git utilities", () => { ); }); + it("returns null for getCurrentBranch in a repo with no commits", async () => { + const emptyRepo = join(tempDir, "empty-repo"); + execSync(`mkdir -p ${emptyRepo}`); + execSync("git init -b main", { cwd: emptyRepo }); + + const branch = await getCurrentBranch(emptyRepo); + expect(branch).toBeNull(); + }); + it("handles status/diff/commit in a normal repo", async () => { writeFileSync(join(repoDir, "file.txt"), "updated\n"); diff --git a/packages/server/src/utils/checkout-git.ts b/packages/server/src/utils/checkout-git.ts index 19669e8bc..7b2567947 100644 --- a/packages/server/src/utils/checkout-git.ts +++ b/packages/server/src/utils/checkout-git.ts @@ -525,12 +525,16 @@ async function requireGitRepo(cwd: string): Promise { } export async function getCurrentBranch(cwd: string): Promise { - const { stdout } = await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { - cwd, - env: READ_ONLY_GIT_ENV, - }); - const branch = stdout.trim(); - return branch.length > 0 ? branch : null; + try { + const { stdout } = await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], { + cwd, + env: READ_ONLY_GIT_ENV, + }); + const branch = stdout.trim(); + return branch.length > 0 ? branch : null; + } catch { + return null; + } } async function getWorktreeRoot(cwd: string): Promise {