diff --git a/apps/host-daemon/src/command-handlers/host-branches.ts b/apps/host-daemon/src/command-handlers/host-branches.ts
index 0f45e65ac..c246cc922 100644
--- a/apps/host-daemon/src/command-handlers/host-branches.ts
+++ b/apps/host-daemon/src/command-handlers/host-branches.ts
@@ -1,7 +1,7 @@
 import path from "node:path";
 import type { GitBranchRefClassification } from "@bb/domain";
 import {
-  detectGitRepo,
+  detectGitSource,
   fetchRemoteBranches,
   getCheckoutRef,
   getGitCommonDir,
@@ -137,7 +137,7 @@ export async function listHostBranches(
     throw new CommandDispatchError("invalid_path", "Path must be absolute");
   }
 
-  if (!(await detectGitRepo(command.path))) {
+  if (!(await detectGitSource(command.path))) {
     return {
       branches: [],
       branchesTruncated: false,
diff --git a/packages/host-workspace/src/git.ts b/packages/host-workspace/src/git.ts
index c60fe6dd1..7f5d204dc 100644
--- a/packages/host-workspace/src/git.ts
+++ b/packages/host-workspace/src/git.ts
@@ -587,23 +587,58 @@ async function findWorkspaceGitOperationMarker(
   return undefined;
 }
 
+export type GitRepoKind = "work-tree" | "bare" | "none";
+
+/**
+ * Classifies `cwd`: inside a work tree, at/inside a bare repository (for
+ * example the "bare clone + sibling worktrees" layout where `<root>/.git`
+ * is a gitdir file pointing at `<root>/.bare`), or not a repository at all.
+ */
+export async function detectGitRepoKind(
+  cwd: string,
+  options: GitTimeoutOptions = {},
+): Promise<GitRepoKind> {
+  const result = await runGit(
+    ["rev-parse", "--is-inside-work-tree", "--is-bare-repository"],
+    { cwd, allowFailure: true, timeoutMs: options.timeoutMs },
+  );
+  if (result.exitCode !== 0) {
+    return "none";
+  }
+  const [insideWorkTree, bare] = trimOutput(result.stdout).split("\n");
+  if (insideWorkTree === "true") {
+    return "work-tree";
+  }
+  if (bare === "true") {
+    return "bare";
+  }
+  return "none";
+}
+
+/** True when `cwd` is inside a git work tree (a checkout). */
 export async function detectGitRepo(
   cwd: string,
   options: GitTimeoutOptions = {},
 ): Promise<boolean> {
-  const result = await runGit(["rev-parse", "--is-inside-work-tree"], {
-    cwd,
-    allowFailure: true,
-    timeoutMs: options.timeoutMs,
-  });
-  return result.exitCode === 0 && trimOutput(result.stdout) === "true";
+  return (await detectGitRepoKind(cwd, options)) === "work-tree";
+}
+
+/**
+ * True when `cwd` is a git repository that can serve as the source of a
+ * worktree: a checkout or a bare repository.
+ */
+export async function detectGitSource(
+  cwd: string,
+  options: GitTimeoutOptions = {},
+): Promise<boolean> {
+  return (await detectGitRepoKind(cwd, options)) !== "none";
 }
 
 export async function ensureGitRepo(
   cwd: string,
   options: GitTimeoutOptions = {},
 ): Promise<void> {
-  if (await detectGitRepo(cwd, options)) {
+  if (await detectGitSource(cwd, options)) {
     return;
   }
 
@@ -619,7 +654,7 @@ export async function readGitRepositoryState(
   cwd: string,
   options: GitTimeoutOptions = {},
 ): Promise<GitRepositoryState> {
-  if (!(await detectGitRepo(cwd, options))) {
+  if (!(await detectGitSource(cwd, options))) {
     return "not_git";
   }
   const result = await runGit(["rev-list", "--all", "--max-count=1"], {
@@ -671,7 +706,7 @@ export async function getCheckoutRef(
   cwd: string,
   options: GitTimeoutOptions = {},
 ): Promise<GitCheckoutRef> {
-  if (!(await detectGitRepo(cwd, options))) {
+  if (!(await detectGitSource(cwd, options))) {
     return { kind: "unknown", reason: "Path is not a git repository" };
   }
 
@@ -867,6 +902,9 @@ export async function getWorkspaceGitOperation(
   cwd: string,
 ): Promise<WorkspaceGitOperation> {
   await ensureGitRepo(cwd);
+  if ((await detectGitRepoKind(cwd)) === "bare") {
+    return { kind: "none" };
+  }
 
   const [gitDir, status] = await Promise.all([
     getAbsoluteGitDir(cwd),
@@ -1467,6 +1505,10 @@ export async function listRemoteBranches(cwd: string): Promise<string[]> {
 
 export async function hasUncommittedChanges(cwd: string): Promise<boolean> {
   await ensureGitRepo(cwd);
+  if ((await detectGitRepoKind(cwd)) === "bare") {
+    // A bare repository has no index or work tree to be dirty.
+    return false;
+  }
   const status = await runGit(
     ["--no-optional-locks", "status", "--porcelain=v1", "--untracked-files=all"],
     { cwd },
diff --git a/packages/host-workspace/src/index.ts b/packages/host-workspace/src/index.ts
index cb9c4a522..2c43d292d 100644
--- a/packages/host-workspace/src/index.ts
+++ b/packages/host-workspace/src/index.ts
@@ -30,6 +30,8 @@ export type {
 export {
   WorkspaceError,
   detectGitRepo,
+  detectGitRepoKind,
+  detectGitSource,
   fetchRemoteBranches,
   getCheckoutRef,
   getCurrentBranch,
