diff --git a/src/infra/update-runner-install-surface.ts b/src/infra/update-runner-install-surface.ts index 7f0cec75d623..15f40e149586 100644 --- a/src/infra/update-runner-install-surface.ts +++ b/src/infra/update-runner-install-surface.ts @@ -64,13 +64,16 @@ export async function resolveGitRoot( runCommand: CommandRunner, candidates: string[], timeoutMs: number, + packageRoot?: string | null, ): Promise { for (const dir of candidates) { const result = await runCommand(["git", "-C", dir, "rev-parse", "--show-toplevel"], { timeoutMs, }).catch(() => null); const root = result?.code === 0 ? result.stdout.trim() : ""; - if (root) { + // A launcher may live inside an unrelated checkout (for example nvm). + // Keep probing until the Git root owns the discovered OpenClaw package. + if (root && (!packageRoot || updateInstallRootsMatch(root, packageRoot))) { return root; } } @@ -117,10 +120,7 @@ export async function resolveUpdateInstallSurface( const candidates = buildStartDirs(opts); const packageRoot = await findPackageRoot(candidates); - let gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs); - if (gitRoot && packageRoot && !updateInstallRootsMatch(gitRoot, packageRoot)) { - gitRoot = null; - } + const gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs, packageRoot); if (gitRoot && !packageRoot) { return { kind: "missing", mode: "unknown", root: resolveUpdateInstallRoot(gitRoot) }; } diff --git a/src/infra/update-runner.test.ts b/src/infra/update-runner.test.ts index 362041475e13..b76d55334eaf 100644 --- a/src/infra/update-runner.test.ts +++ b/src/infra/update-runner.test.ts @@ -302,6 +302,41 @@ describe("runGatewayUpdate", () => { }, ); + it("skips an unrelated enclosing git root before the OpenClaw checkout", async () => { + const versionManagerRoot = path.join(tempDir, "version-manager"); + const binDir = path.join(versionManagerRoot, "bin"); + const sourceRoot = path.join(tempDir, "source"); + await Promise.all([ + fs.mkdir(binDir, { recursive: true }), + fs.mkdir(sourceRoot, { recursive: true }), + ]); + await fs.writeFile( + path.join(sourceRoot, "package.json"), + JSON.stringify({ name: "openclaw", version: "1.0.0" }), + "utf8", + ); + + const { runner, calls } = createRunner({ + [`git -C ${binDir} rev-parse --show-toplevel`]: { stdout: versionManagerRoot }, + [`git -C ${sourceRoot} rev-parse --show-toplevel`]: { stdout: sourceRoot }, + }); + + await expect( + resolveUpdateInstallSurface({ + argv1: path.join(binDir, "openclaw"), + cwd: sourceRoot, + timeoutMs: 1000, + runCommand: runner, + }), + ).resolves.toMatchObject({ + kind: "git", + mode: "git", + root: sourceRoot, + packageRoot: sourceRoot, + }); + expect(calls).toContain(`git -C ${sourceRoot} rev-parse --show-toplevel`); + }); + async function setupUiIndex() { const uiIndexPath = path.join(tempDir, "dist", "control-ui", "index.html"); await fs.mkdir(path.dirname(uiIndexPath), { recursive: true }); diff --git a/src/infra/update-runner.ts b/src/infra/update-runner.ts index 9937e361c094..65830154eab7 100644 --- a/src/infra/update-runner.ts +++ b/src/infra/update-runner.ts @@ -32,7 +32,7 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise< const candidates = buildStartDirs(opts); const pkgRoot = await findPackageRoot(candidates); - let gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs); + let gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs, pkgRoot); if (!gitRoot && pkgRoot) { const cwdRoot = normalizeDir(opts.cwd); if ( @@ -43,9 +43,6 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise< gitRoot = resolveUpdateInstallRoot(cwdRoot); } } - if (gitRoot && pkgRoot && !updateInstallRootsMatch(gitRoot, pkgRoot)) { - gitRoot = null; - } if (gitRoot && !pkgRoot) { return { status: "error", @@ -56,7 +53,7 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise< durationMs: Date.now() - startedAt, }; } - if (gitRoot && pkgRoot && updateInstallRootsMatch(gitRoot, pkgRoot)) { + if (gitRoot && pkgRoot) { return await updateGitCheckout({ opts, gitRoot,