fix(wsl): retry the setup-runner .git link read and fall back to the distro's Git (#36792)

* fix(wsl): retry the setup-runner .git link read and fall back to the distro's Git

A Windows stat over \\wsl.localhost can report a transient ENOENT. Retry the link read once,
then ask the distro's Git asynchronously (fenced) before failing setup-script creation.

* fix(wsl): map a mounted-drive Git answer to its Windows drive
This commit is contained in:
Neil
2026-10-31 16:22:24 -07:00
committed by GitHub
parent 9c29a922d6
commit 13f390f4f5
19 changed files with 214 additions and 71 deletions
@@ -0,0 +1,96 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { gitExecFileAsyncMock, readWorktreeGitPathMock } = vi.hoisted(() => ({
gitExecFileAsyncMock: vi.fn(),
readWorktreeGitPathMock: vi.fn()
}))
vi.mock('./command-runner/git-exec-file', () => ({ gitExecFileAsync: gitExecFileAsyncMock }))
vi.mock('./worktree-git-path', () => ({ readWorktreeGitPath: readWorktreeGitPathMock }))
vi.mock('../wsl/wsl-automount-root', async (importOriginal) => ({
...(await importOriginal<object>()),
knownWslAutomountRoots: () => ['/mnt/']
}))
import { resolveWslWorktreeGitPath } from './wsl-worktree-git-path'
const WORKTREE = '\\\\wsl.localhost\\Ubuntu\\home\\jin\\feature'
const FROM_LINK =
'\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo\\.git\\worktrees\\feature\\orca\\setup-runner.sh'
describe('resolveWslWorktreeGitPath', () => {
beforeEach(() => {
gitExecFileAsyncMock.mockReset()
readWorktreeGitPathMock.mockReset()
})
it('uses the .git link without running Git', async () => {
readWorktreeGitPathMock.mockReturnValue(FROM_LINK)
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
FROM_LINK
)
expect(readWorktreeGitPathMock).toHaveBeenCalledWith(WORKTREE, 'orca/setup-runner.sh', [
'/mnt/'
])
expect(gitExecFileAsyncMock).not.toHaveBeenCalled()
})
it('retries the link once after a transient miss', async () => {
readWorktreeGitPathMock.mockReturnValueOnce(null).mockReturnValueOnce(FROM_LINK)
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
FROM_LINK
)
expect(readWorktreeGitPathMock).toHaveBeenCalledTimes(2)
expect(gitExecFileAsyncMock).not.toHaveBeenCalled()
})
it("asks the distro's Git when the link stays unreadable", async () => {
readWorktreeGitPathMock.mockReturnValue(null)
gitExecFileAsyncMock.mockResolvedValue({
stdout: '/home/jin/repo/.git/worktrees/feature/orca/setup-runner.sh\n',
stderr: ''
})
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
FROM_LINK
)
expect(readWorktreeGitPathMock).toHaveBeenCalledTimes(2)
expect(gitExecFileAsyncMock).toHaveBeenCalledWith(
['rev-parse', '--git-path', 'orca/setup-runner.sh'],
{ cwd: WORKTREE }
)
})
it("maps Git's mounted-drive answer to the Windows drive", async () => {
readWorktreeGitPathMock.mockReturnValue(null)
gitExecFileAsyncMock.mockResolvedValue({
stdout: '/mnt/c/src/repo/.git/worktrees/feature/orca/setup-runner.sh\n',
stderr: ''
})
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
'C:\\src\\repo\\.git\\worktrees\\feature\\orca\\setup-runner.sh'
)
})
it("resolves Git's worktree-relative answer against the worktree", async () => {
readWorktreeGitPathMock.mockReturnValue(null)
gitExecFileAsyncMock.mockResolvedValue({ stdout: '.git/orca/setup-runner.sh\n', stderr: '' })
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
'\\\\wsl.localhost\\Ubuntu\\home\\jin\\feature\\.git\\orca\\setup-runner.sh'
)
})
it("names the worktree and keeps Git's error when both fail", async () => {
readWorktreeGitPathMock.mockReturnValue(null)
const gitError = new Error('fatal: not a git repository')
gitExecFileAsyncMock.mockRejectedValue(gitError)
const failure = resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')
await expect(failure).rejects.toThrow(/couldn't find this WSL worktree's Git folder/)
await expect(failure).rejects.toMatchObject({ cause: gitError })
})
})
+46
View File
@@ -0,0 +1,46 @@
import { win32 } from 'node:path'
import { parseWslUncPath, toWindowsWslUncPath } from '../../shared/wsl-paths'
import { knownWslAutomountRoots, wslDrivePathUnderRoots } from '../wsl/wsl-automount-root'
import { gitExecFileAsync } from './command-runner/git-exec-file'
import { readWorktreeGitPath } from './worktree-git-path'
/**
* A per-worktree Git path for a worktree inside a distro, as a path Windows can write. Reads the
* `.git` link first; the distro's own Git answers when that read fails.
*/
export async function resolveWslWorktreeGitPath(
worktreePath: string,
relativePath: string
): Promise<string> {
const automountRoots = knownWslAutomountRoots()
const read = (): string | null => readWorktreeGitPath(worktreePath, relativePath, automountRoots)
// A Windows stat over \\wsl.localhost can report a transient ENOENT.
const fromLink = read() ?? read()
if (fromLink) {
return fromLink
}
const distro = parseWslUncPath(worktreePath)?.distro
try {
// Async and fenced; the caller is a user act, so starting the distro is fine here.
const { stdout } = await gitExecFileAsync(['rev-parse', '--git-path', relativePath], {
cwd: worktreePath
})
const gitPath = stdout.trim()
if (gitPath && distro) {
// A git dir on a mounted drive maps to the drive itself, as the link read does.
return gitPath.startsWith('/')
? (wslDrivePathUnderRoots(gitPath, automountRoots) ?? toWindowsWslUncPath(gitPath, distro))
: win32.join(worktreePath, gitPath)
}
} catch (error) {
throw wslWorktreeGitPathError(worktreePath, error)
}
throw wslWorktreeGitPathError(worktreePath)
}
function wslWorktreeGitPathError(worktreePath: string, cause?: unknown): Error {
return new Error(
`Orca couldn't find this WSL worktree's Git folder to write its setup script. Check that ${worktreePath} is a Git worktree.`,
cause === undefined ? undefined : { cause }
)
}
+7 -7
View File
@@ -60,7 +60,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
makeRepo(),
'C:\\repo\\feature\\',
'pnpm install\npnpm build'
@@ -105,7 +105,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
{ ...makeRepo(), path: 'C:\\Users\\jinwo\\git\\orca' },
'C:\\repo\\feature',
'#!/usr/bin/env bash\npnpm install',
@@ -150,7 +150,7 @@ describe('createSetupRunnerScript', () => {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const { TERMINAL_GIT_CREDENTIAL_GUARD_POLICY_ENV } =
await import('../shared/terminal-git-credential-guard')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
makeRepo(),
'C:\\repo\\feature',
'pnpm install',
@@ -172,7 +172,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
{ ...makeRepo(), path: 'C:\\Users\\jinwo\\git\\orca' },
'C:\\repo\\feature',
'pnpm install'
@@ -255,7 +255,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(makeRepo(), '/test/repo-feature', 'pnpm install')
const result = await createSetupRunnerScript(makeRepo(), '/test/repo-feature', 'pnpm install')
expect(result.envVars).toEqual(
expect.objectContaining({
@@ -284,7 +284,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
{
...makeRepo(),
path: 'C:\\Users\\jinwo\\git\\orca'
@@ -338,7 +338,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
makeRepo(),
'\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo\\feature',
'pnpm install'
+31 -26
View File
@@ -101,7 +101,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
makeRepo(),
'C:\\repo-worktree',
'#!/usr/bin/env bash\r\npnpm install\r\nnpm run build',
@@ -140,7 +140,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
makeRepo(),
'C:\\repo-worktree',
'copy .env.example .env\r\nxcopy /E assets dist',
@@ -177,7 +177,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const { buildSetupRunnerCommand } = await import('../shared/setup-runner-command')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
makeRepo(),
'C:\\repo-worktree',
'copy .env.example .env',
@@ -207,7 +207,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
createSetupRunnerScript(
await createSetupRunnerScript(
makeRepo(),
'C:\\repo-worktree',
'#!/usr/bin/env -S bash -euo pipefail\nmake build | tee build.log',
@@ -236,7 +236,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
makeRepo(),
'C:\\repo-worktree',
'pnpm install\nnpm run build',
@@ -277,7 +277,7 @@ describe('createSetupRunnerScript', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(makeRepo(), '/test/worktree', 'pnpm install')
const result = await createSetupRunnerScript(makeRepo(), '/test/worktree', 'pnpm install')
expect(gitExecFileSyncMock).toHaveBeenCalledWith(
['rev-parse', '--git-path', 'orca/setup-runner.sh'],
@@ -301,34 +301,39 @@ describe('createSetupRunnerScript', () => {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
expect(
createSetupRunnerScript(makeRepo(), '/test/worktree', 'echo setup').waitForAgentStartup
).toBeUndefined()
expect(
createSetupRunnerScript(makeRepo('start-immediately'), '/test/worktree', 'echo setup')
(await createSetupRunnerScript(makeRepo(), '/test/worktree', 'echo setup'))
.waitForAgentStartup
).toBeUndefined()
expect(
createSetupRunnerScript(makeRepo('wait-for-setup'), '/test/worktree', 'echo setup')
(await createSetupRunnerScript(makeRepo('start-immediately'), '/test/worktree', 'echo setup'))
.waitForAgentStartup
).toBeUndefined()
expect(
(await createSetupRunnerScript(makeRepo('wait-for-setup'), '/test/worktree', 'echo setup'))
.waitForAgentStartup
).toBe(true)
expect(
createSetupRunnerScript(
makeRepo('start-immediately'),
'/test/worktree',
'echo setup',
undefined,
undefined,
'wait-for-setup'
(
await createSetupRunnerScript(
makeRepo('start-immediately'),
'/test/worktree',
'echo setup',
undefined,
undefined,
'wait-for-setup'
)
).waitForAgentStartup
).toBe(true)
expect(
createSetupRunnerScript(
makeRepo('wait-for-setup'),
'/test/worktree',
'echo setup',
undefined,
undefined,
'start-immediately'
(
await createSetupRunnerScript(
makeRepo('wait-for-setup'),
'/test/worktree',
'echo setup',
undefined,
undefined,
'start-immediately'
)
).waitForAgentStartup
).toBe(true)
})
@@ -338,7 +343,7 @@ describe('createSetupRunnerScript', () => {
gitExecFileSyncMock.mockReturnValue('/test/repo/.git/orca/setup-runner.sh\n')
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const setup = createSetupRunnerScript(makeRepo(), '/test/worktree', 'git fetch')
const setup = await createSetupRunnerScript(makeRepo(), '/test/worktree', 'git fetch')
expect(setup.envVars).toMatchObject({
ORCA_ROOT_PATH: '/test/repo',
+1 -1
View File
@@ -326,7 +326,7 @@ describe('runHook', () => {
try {
const { createSetupRunnerScript } = await import('./worktree-runner-script')
const result = createSetupRunnerScript(
const result = await createSetupRunnerScript(
{
...makeRepo(),
path: 'C:\\Users\\jinwo\\git\\orca'
+1 -1
View File
@@ -1701,7 +1701,7 @@ async function performLocalWorktreeCreate(
if (setupScript && shouldLaunchSetup) {
try {
// Main generates setup scripts without executing them; generation failure leaves the created workspace usable.
setup = createSetupRunnerScript(
setup = await createSetupRunnerScript(
repo,
worktreePath,
setupScript,
@@ -148,7 +148,7 @@ describe('automation creates: main provisions setup and default tabs', () => {
])(
'runs a $platform runner with the $platform command',
async ({ runnerScriptPath, platform }) => {
createSetupRunnerScriptMock.mockReturnValueOnce({ runnerScriptPath, envVars: {} })
createSetupRunnerScriptMock.mockResolvedValueOnce({ runnerScriptPath, envVars: {} })
await handlers['worktrees:create'](null, {
repoId: 'repo-1',
@@ -758,7 +758,7 @@ describe('registerWorktreeHandlers', () => {
getEffectiveHooksMock.mockReturnValue({ scripts: { setup: 'pnpm install' } })
getEffectiveHooksFromConfigMock.mockReturnValue({ scripts: { setup: 'pnpm install' } })
shouldRunSetupForCreateMock.mockReturnValue(true)
createSetupRunnerScriptMock.mockReturnValueOnce({
createSetupRunnerScriptMock.mockResolvedValueOnce({
runnerScriptPath: 'C:\\workspace\\repo\\.git\\orca\\setup-runner.sh',
shell: { family: 'posix', executable: 'wsl.exe' },
envVars: {
+1 -1
View File
@@ -253,7 +253,7 @@ export function setupWorktreeHandlers(): WorktreeRuntimeStub {
GHOSTX_ROOT_PATH: repoArg.path
})
)
createSetupRunnerScriptMock.mockReturnValue({
createSetupRunnerScriptMock.mockResolvedValue({
runnerScriptPath: '/workspace/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/workspace/repo',
@@ -63,7 +63,7 @@ async function createFromRendererPayload(options: {
}
])
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm install' } })
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: RUNNER,
envVars: SETUP_ENV
})
@@ -70,7 +70,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: 'C:\\tmp\\repo\\.git\\orca\\setup-runner.sh',
shell: { family: 'posix', executable: 'wsl.exe' },
envVars: {
@@ -167,7 +167,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -243,7 +243,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: 'C:\\tmp\\repo\\.git\\orca\\setup-runner.sh',
shell: { family: 'posix', executable: 'wsl.exe' },
envVars: {
@@ -557,7 +557,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -65,7 +65,7 @@ describe('OrcaRuntimeService', () => {
ensurePathWithinWorkspaceMock.mockReturnValue('/tmp/workspaces/runtime-bg-split-setup')
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm install' } })
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -146,7 +146,7 @@ describe('OrcaRuntimeService', () => {
ensurePathWithinWorkspaceMock.mockReturnValue('/tmp/workspaces/runtime-active-split-setup')
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm install' } })
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -309,7 +309,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -441,7 +441,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: 'C:\\tmp\\repo\\.git\\orca\\setup-runner.sh',
shell: { family: 'posix', executable: 'wsl.exe' },
envVars: {
@@ -44,7 +44,7 @@ describe('OrcaRuntimeService', () => {
setup: 'pnpm worktree:setup'
}
})
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -132,7 +132,7 @@ describe('OrcaRuntimeService', () => {
setup: 'pnpm worktree:setup'
}
})
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -199,7 +199,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(resolveSetupRunnerShell).mockReturnValue({ family: 'posix' })
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: 'C:\\repo\\.git\\orca\\setup-runner.sh',
shell: { family: 'posix' },
envVars: {
@@ -282,7 +282,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
envVars: {
ORCA_ROOT_PATH: '/tmp/repo',
@@ -396,7 +396,7 @@ describe('OrcaRuntimeService', () => {
}
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: 'C:\\repo\\.git\\orca\\setup-runner.sh',
shell: { family: 'posix', executable: 'wsl.exe' },
envVars: {
@@ -474,7 +474,7 @@ describe('OrcaRuntimeService', () => {
})
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(resolveSetupRunnerShell).mockReturnValue({ family: 'posix' })
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: 'C:\\repo\\.git\\orca\\setup-runner.sh',
shell: { family: 'posix' },
envVars: {
@@ -79,7 +79,7 @@ async function createActivated(options: {
ensurePathWithinWorkspaceMock.mockReturnValue(WORKTREE_PATH)
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm worktree:setup' } })
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: RUNNER,
envVars: { ORCA_ROOT_PATH: '/tmp/repo', ORCA_WORKTREE_PATH: WORKTREE_PATH }
})
@@ -71,7 +71,7 @@ describe('host worktree creation while its renderer is unavailable', () => {
])
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'echo setup' } })
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/tmp/setup-runner.sh',
envVars: {}
})
@@ -57,7 +57,7 @@ export async function prepareRuntimeLocalWorktreeSetup(args: {
if (shouldRunSetup && hooks?.scripts.setup) {
if (args.shouldUseSetupRunner) {
try {
setup = createSetupRunnerScript(
setup = await createSetupRunnerScript(
repo,
worktreePath,
hooks.scripts.setup,
@@ -84,7 +84,7 @@ function chatCreateRuntime(args: {
ensurePathWithinWorkspaceMock.mockReturnValue(path)
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm install' } })
vi.mocked(getDefaultTabsLaunch).mockReturnValue(args.withDefaults ? DEFAULTS : undefined)
vi.mocked(createSetupRunnerScript).mockReturnValue({
vi.mocked(createSetupRunnerScript).mockResolvedValue({
runnerScriptPath: '/repo/setup.sh',
envVars: {},
...(args.wait ? { waitForAgentStartup: true } : {})
+10 -14
View File
@@ -8,6 +8,7 @@ import { resolveWindowsGitBashShellPath } from './git-bash'
import { gitExecFileSync } from './git/runner'
import { isLocalGitRuntimeRouterInstalled } from './git/command-runner/local-git-runtime-route'
import { readWorktreeGitPath } from './git/worktree-git-path'
import { resolveWslWorktreeGitPath } from './git/wsl-worktree-git-path'
import { knownWslAutomountRoots } from './wsl/wsl-automount-root'
import { isWslPath, toWindowsWslPath, toLinuxPath } from './wsl'
import { getHookRuntimeTarget, getHookWslContext } from './hook-runtime-target'
@@ -30,14 +31,14 @@ function getGitPath(cwd: string, relativePath: string): string {
}).trim()
}
export function createSetupRunnerScript(
export async function createSetupRunnerScript(
repo: Repo,
worktreePath: string,
script: string,
projectRuntime?: ProjectExecutionRuntimeResolution | HookRuntimeTarget,
setupShell?: SetupRunnerShell,
projectStartupPolicy?: SetupAgentStartupPolicy
): WorktreeSetupLaunch {
): Promise<WorktreeSetupLaunch> {
return createWorktreeRunnerScript({
repo,
worktreePath,
@@ -52,7 +53,7 @@ export function createSetupRunnerScript(
})
}
function createWorktreeRunnerScript(args: {
async function createWorktreeRunnerScript(args: {
repo: Repo
worktreePath: string
script: string
@@ -60,7 +61,7 @@ function createWorktreeRunnerScript(args: {
runtimeTarget?: HookRuntimeTarget
waitForAgentStartup?: boolean
setupShell?: SetupRunnerShell
}): WorktreeSetupLaunch {
}): Promise<WorktreeSetupLaunch> {
const {
repo,
worktreePath,
@@ -88,18 +89,13 @@ function createWorktreeRunnerScript(args: {
// Linked worktrees have .git files; resolve the per-worktree gitdir through Git.
const runnerExtension = runnerShell.family === 'cmd' ? 'cmd' : 'sh'
const gitRelPath = `orca/${runnerBaseName}.${runnerExtension}`
// Read the link with the D1c Git router (if WSL Git made the worktree, host Git can't), and always
// for a worktree inside a distro: Git there would be a synchronous wsl.exe on the main thread.
const inDistro = isWslPath(worktreePath)
const fromLink =
isLocalGitRuntimeRouterInstalled() || inDistro
// Read the link with the D1c Git router (if WSL Git made the worktree, host Git can't). A worktree
// inside a distro never runs synchronous Git: that would be a synchronous wsl.exe on the main thread.
const fromLink = isWslPath(worktreePath)
? await resolveWslWorktreeGitPath(worktreePath, gitRelPath)
: isLocalGitRuntimeRouterInstalled()
? readWorktreeGitPath(worktreePath, gitRelPath, knownWslAutomountRoots())
: null
if (!fromLink && inDistro) {
throw new Error(
`Orca couldn't find this WSL worktree's Git folder to write its setup script. Check that ${worktreePath} is a Git worktree.`
)
}
let runnerScriptPath = fromLink ?? getGitPath(worktreePath, gitRelPath)
// Convert WSL Git's Linux path to a path Windows filesystem calls can reach.