diff --git a/packages/core/src/__tests__/git-repository.test.ts b/packages/core/src/__tests__/git-repository.test.ts index bb0cff60ca..973109b6b4 100644 --- a/packages/core/src/__tests__/git-repository.test.ts +++ b/packages/core/src/__tests__/git-repository.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from "vitest"; import { execFile } from "node:child_process"; -import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { promisify } from "node:util"; @@ -107,4 +107,17 @@ describe("ensureGitRepositoryForProjectPath", () => { ensureGitRepositoryForProjectPath(projectPath, { runner }), ).rejects.toBeInstanceOf(GitRepositoryInitializationError); }); + + it("skips git init for a workspace-mode project root (.fusion/workspace.json present)", async () => { + const projectPath = tempDir("fusion-git-workspace-"); + // Simulate workspace init: .fusion/workspace.json exists, root is non-git + mkdirSync(join(projectPath, ".fusion"), { recursive: true }); + writeFileSync(join(projectPath, ".fusion", "workspace.json"), JSON.stringify({ repos: ["repo-a"] })); + + const outcome = await ensureGitRepositoryForProjectPath(projectPath); + + expect(outcome).toBe("existing"); + // No .git should be created at the workspace root + expect(existsSync(join(projectPath, ".git"))).toBe(false); + }); }); diff --git a/packages/core/src/git-repository.ts b/packages/core/src/git-repository.ts index adaebeb2ed..2199bec314 100644 --- a/packages/core/src/git-repository.ts +++ b/packages/core/src/git-repository.ts @@ -41,6 +41,20 @@ export async function ensureGitRepositoryForProjectPath( const runner = options.runner ?? runGitCommand; const timeout = options.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS; + /* + FNXC:Workspace 2026-06-24-10:00: + A workspace-mode project root is intentionally NOT a git repository — it is a parent + directory containing multiple git sub-repos (detected at init time and recorded in + .fusion/workspace.json). Running `git init` here would create a stray empty repo at the + workspace root, poisoning every downstream git command: the executor sets the session cwd + to this root (browse-only), and `git rev-parse --abbrev-ref HEAD` fails on the unborn HEAD + with "ambiguous argument 'HEAD'". Detect workspace mode via the config file and skip the + git-init so the root stays non-git, matching the workspace execution contract (KTD1). + */ + if (await loadWorkspaceConfig(projectPath)) { + return "existing"; + } + if (await isInsideGitWorkTree(projectPath, runner, timeout)) { return "existing"; }