diff --git a/packages/adapter-utils/src/execution-target.ts b/packages/adapter-utils/src/execution-target.ts index 7ba9109809..06b8ff781e 100644 --- a/packages/adapter-utils/src/execution-target.ts +++ b/packages/adapter-utils/src/execution-target.ts @@ -1650,7 +1650,9 @@ printf '\0PAPERCLIP_GIT_CONTEXT_END\0' `; const result = await adapterExecutionTargetCommandRunner(remote).execute({ command: "sh", args: ["-c", probe, "paperclip-git-context", input.hostCredentials ? "host" : "managed"], - cwd: input.cwd, timeoutMs: 15_000, + // The caller's cwd belongs to the controller. Copied sandbox/SSH + // workspaces can live at a different path on the execution target. + cwd: remote.remoteCwd, timeoutMs: 15_000, }); if (result.exitCode !== 0) throw new Error("Could not read execution-target Git context"); const payload = result.stdout.split("\0PAPERCLIP_GIT_CONTEXT_V1\0")[1]?.split("\0PAPERCLIP_GIT_CONTEXT_END\0")[0]; diff --git a/packages/adapter-utils/src/github-launcher-environment.test.ts b/packages/adapter-utils/src/github-launcher-environment.test.ts index 4534b4dec0..8d217e602c 100644 --- a/packages/adapter-utils/src/github-launcher-environment.test.ts +++ b/packages/adapter-utils/src/github-launcher-environment.test.ts @@ -1,5 +1,5 @@ import { execFile } from "node:child_process"; -import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { mkdtemp, mkdir, readFile, realpath, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; @@ -66,6 +66,41 @@ async function sandbox(layout: string) { } describe("managed GitHub launcher environment", () => { + it.each([false, true])("probes the remote workspace when the controller cwd is absent (host credentials: %s)", async (hostCredentials) => { + const fixture = await sandbox("usr/bin"); + const env = await prepareGitHubExecutionEnvironment({ + target: fixture.target, + cwd: path.join(fixture.root, "controller-only", "agent-workspace"), + env: {}, + hostCredentials, + networkAccess: true, + }); + + expect(env.PAPERCLIP_RUNNER_NETWORK_ACCESS).toBe("enabled"); + expect(env.PAPERCLIP_GIT_METADATA_ROOTS).toBe("[]"); + expect(JSON.parse(env.PAPERCLIP_RUNNER_NETWORK_ROOTS!)).not.toHaveLength(0); + expect(fixture.runner.execute).toHaveBeenCalledWith(expect.objectContaining({ cwd: fixture.root })); + }); + + it("reads Git metadata from the SSH workspace instead of an existing controller directory", async () => { + const fixture = await sandbox("ssh-toolchain/bin"); + // Use real Git for the probe, not the launcher fixture's stub. + await rm(path.join(fixture.bin, "git")); + await exec("git", ["init", fixture.root]); + const controllerCwd = path.join(fixture.root, "controller"); + await mkdir(controllerCwd); + vi.spyOn(ssh, "createSshCommandManagedRuntimeRunner").mockReturnValue(fixture.runner); + const target = { kind: "remote" as const, transport: "ssh" as const, remoteCwd: fixture.root, + spec: { host: "sandbox.example.test", port: 22, username: "runner", remoteCwd: fixture.root, + remoteWorkspacePath: fixture.root, privateKey: null, knownHosts: null, strictHostKeyChecking: true } }; + + const env = await prepareGitHubExecutionEnvironment({ + target, cwd: controllerCwd, env: {}, hostCredentials: false, networkAccess: true, + }); + + expect(JSON.parse(env.PAPERCLIP_GIT_METADATA_ROOTS!)).toEqual([await realpath(path.join(fixture.root, ".git"))]); + }); + it("uses target Git configuration without importing controller credentials", async () => { const fixture = await sandbox("usr/bin"); vi.stubEnv("GH_TOKEN", "controller-secret");