diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index 6d815452211..c1cfd6f47a6 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -7159,6 +7159,45 @@ describe('OrcaRuntimeService', () => { } }) + it('refuses SSH hosts instead of setting the project up on the local machine', async () => { + const spawnSpy = vi.spyOn(gitRunner, 'gitSpawn') + const repos: Record[] = [] + const runtimeStore = { + ...store, + getRepos: () => [...repos] as never, + addRepo: (repo: Record) => { + repos.push(repo) + } + } + const runtime = new OrcaRuntimeService(runtimeStore as never) + + try { + await expect( + runtime.setupProjectClone({ + projectId: 'github:stablyai/orca', + hostId: 'ssh:openclaw', + url: 'https://example.com/orca.git', + destination: '/home/brennan' + }) + ).rejects.toThrow(/SSH hosts are not supported/) + + await expect( + runtime.setupProjectExistingFolder({ + projectId: 'github:stablyai/orca', + hostId: 'ssh:openclaw', + path: '/home/brennan/orca', + kind: 'git' + }) + ).rejects.toThrow(/SSH hosts are not supported/) + + // Why: the defect was a silent local clone/probe recorded as remote, not a bad message. + expect(spawnSpy).not.toHaveBeenCalled() + expect(repos).toHaveLength(0) + } finally { + spawnSpy.mockRestore() + } + }) + it('adopts public clone repos into host-qualified project setup', async () => { const destination = await mkdtemp(join(tmpdir(), 'orca-runtime-project-clone-')) const clonePath = join(destination, 'orca') diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index 6e66cda4e60..ce66d582c79 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -1801,6 +1801,19 @@ function runtimeRepoMatchesExecutionHost( return repo.connectionId == null } +// Why: this runtime only has local git and local fs, so an ssh: host here would clone and +// probe the wrong machine and then register the result as remote. SSH setup is owned by the +// desktop IPC path (addRemoteRepoFromPath / cloneRemoteRepo), which the renderer routes to; +// only `local` and `runtime:` legitimately reach these RPCs. +function assertProjectHostSetupHostIsSupported(hostId: ExecutionHostId | null | undefined): void { + if (parseExecutionHostId(hostId)?.kind !== 'ssh') { + return + } + throw new Error( + 'SSH hosts are not supported by this operation. Set the project up from the Orca desktop app, which owns the SSH connection.' + ) +} + function getRuntimeFolderWorkspaceInstanceId(repo: Repo, instanceId: string): string { return `${getRuntimeFolderWorkspaceRootId(repo)}${FOLDER_WORKSPACE_INSTANCE_SEPARATOR}${instanceId}` } @@ -14929,6 +14942,7 @@ export class OrcaRuntimeService { if (!this.store) { throw new Error('runtime_unavailable') } + assertProjectHostSetupHostIsSupported(args.hostId) let repo = await this.addRepo(args.path, args.kind === 'folder' ? 'folder' : 'git', args.hostId) let setup = getProjectHostSetupForRepo(this.listProjectHostSetups(), repo) if (setup.projectId !== args.projectId) { @@ -14971,6 +14985,8 @@ export class OrcaRuntimeService { } async setupProjectClone(args: ProjectHostSetupCloneArgs): Promise { + // Why: guard before cloneRepo, which would otherwise clone to the local disk. + assertProjectHostSetupHostIsSupported(args.hostId) const repo = await this.cloneRepo(args.url, args.destination, args.hostId) return await this.setupProjectExistingFolder({ projectId: args.projectId,