Fix fresh workspace ambient credential probing

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-19 17:16:28 +00:00
1 parent 014390c9c0
commit 309aa0f33f
3 files changed
+145 -29

No files matched your search

@@ -479,7 +479,7 @@ describe("resolveExecutionRunAdapterConfig", () => {
} as any,
});
expect(probe).toHaveBeenCalledWith("/checkout");
expect(probe).toHaveBeenCalledWith("/checkout", { configuredPushUrls: undefined });
expect(result.resolvedConfig.env).toEqual({ AGENT_ONLY: "agent-only" });
expect(result.secretKeys).toEqual(new Set());
expect(getByNameInsensitive).not.toHaveBeenCalled();
@@ -24,6 +24,7 @@ import {
preflightLowTrustWorkspaceIsolation,
probeAmbientHostPushCredential,
prioritizeProjectWorkspaceCandidatesForRun,
resolveAmbientHostCredentialCheckout,
parseSessionCompactionPolicy,
provisionExecutionWorkspaceForFreshnessDecision,
reconcileReusedExecutionWorkspaceProjectWorkspaceId,
@@ -1017,6 +1018,52 @@ describe("probeAmbientHostPushCredential", () => {
execFile: execFileMock as unknown as typeof execFile,
})).resolves.toBeNull();
});
it("accepts the selected fresh workspace SSH URL before its checkout exists", async () => {
const execFileMock = vi.fn(async (command: string) => {
if (command === "gh") throw Object.assign(new Error("not logged in"), { code: 1 });
throw Object.assign(new Error("checkout not realized"), { code: "ENOENT" });
});
await expect(probeAmbientHostPushCredential("/future/checkout", {
execFile: execFileMock as unknown as typeof execFile,
configuredPushUrls: ["git@github.com:example/repo.git"],
})).resolves.toBe("ssh_push_remote");
expect(execFileMock).toHaveBeenCalledTimes(2);
});
});
describe("resolveAmbientHostCredentialCheckout", () => {
it("uses the preferred fresh project workspace instead of the static adapter cwd", () => {
expect(resolveAmbientHostCredentialCheckout({
adapterCwd: "/static/adapter",
preferredProjectWorkspaceId: "workspace-2",
projectWorkspaces: [
{ id: "workspace-1", cwd: "/checkout/one", repoUrl: "https://github.com/example/one.git" },
{ id: "workspace-2", cwd: "/__paperclip_repo_only__", repoUrl: "git@github.com:example/two.git" },
],
})).toEqual({
checkoutCwd: "/static/adapter",
configuredPushUrls: ["git@github.com:example/two.git"],
});
});
it("prefers a reusable realized checkout over project and adapter paths", () => {
expect(resolveAmbientHostCredentialCheckout({
adapterCwd: "/static/adapter",
preferredProjectWorkspaceId: "workspace-1",
projectWorkspaces: [
{ id: "workspace-1", cwd: "/project/checkout", repoUrl: "git@github.com:example/project.git" },
],
reusableExecutionWorkspace: {
cwd: "/reused/checkout",
repoUrl: "ssh://git@github.com/example/reused.git",
},
})).toEqual({
checkoutCwd: "/reused/checkout",
configuredPushUrls: ["ssh://git@github.com/example/reused.git"],
});
});
});
describe("shouldProbeAmbientHostPushCredential", () => {
+97 -28
View File
@@ -855,10 +855,14 @@ export type AmbientHostPushCredential = "gh_cli" | "ssh_push_remote";
*/
export async function probeAmbientHostPushCredential(
cwd: string | null | undefined,
deps: { execFile: typeof execFile } = { execFile },
deps: {
execFile?: typeof execFile;
configuredPushUrls?: readonly string[];
} = {},
): Promise<AmbientHostPushCredential | null> {
const execFileImpl = deps.execFile ?? execFile;
try {
const result = await deps.execFile(
const result = await execFileImpl(
"gh",
["auth", "token", "--hostname", "github.com"],
{ timeout: AMBIENT_GITHUB_CREDENTIAL_PROBE_TIMEOUT_MS, windowsHide: true },
@@ -868,40 +872,75 @@ export async function probeAmbientHostPushCredential(
// A missing CLI, timeout, non-zero exit, or unreadable auth state is a tier miss.
}
const pushUrls: string[] = [];
const normalizedCwd = readNonEmptyString(cwd);
if (!normalizedCwd) return null;
const remoteNames = await deps.execFile(
"git",
["remote"],
{
cwd: normalizedCwd,
timeout: AMBIENT_GITHUB_CREDENTIAL_PROBE_TIMEOUT_MS,
windowsHide: true,
},
).then((result) =>
result.stdout
.split(/\r?\n/)
.map((value) => value.trim())
.filter((value) => value.length > 0),
).catch(() => []);
for (const remoteName of remoteNames) {
const pushUrl = await deps.execFile(
if (normalizedCwd) {
const remoteNames = await execFileImpl(
"git",
["remote", "get-url", "--push", remoteName],
["remote"],
{
cwd: normalizedCwd,
timeout: AMBIENT_GITHUB_CREDENTIAL_PROBE_TIMEOUT_MS,
windowsHide: true,
},
).then((result) => readNonEmptyString(result.stdout)).catch(() => null);
if (pushUrl && (/^ssh:\/\//i.test(pushUrl) || /^[^\s/@:]+@[^\s/:]+:.+/.test(pushUrl))) {
return "ssh_push_remote";
).then((result) =>
result.stdout
.split(/\r?\n/)
.map((value) => value.trim())
.filter((value) => value.length > 0),
).catch(() => []);
for (const remoteName of remoteNames) {
const pushUrl = await execFileImpl(
"git",
["remote", "get-url", "--push", remoteName],
{
cwd: normalizedCwd,
timeout: AMBIENT_GITHUB_CREDENTIAL_PROBE_TIMEOUT_MS,
windowsHide: true,
},
).then((result) => readNonEmptyString(result.stdout)).catch(() => null);
if (pushUrl) pushUrls.push(pushUrl);
}
}
// A fresh workspace has no checkout to inspect yet. Its selected project-workspace URL becomes
// the clone's origin, so treat that URL as the future push remote. This remains deliberately
// network-free: the tier identifies SSH transport, while the eventual push remains the authority
// for key, agent, host, and repository authorization.
pushUrls.push(...(deps.configuredPushUrls ?? []));
if (pushUrls.some((pushUrl) => (
/^ssh:\/\//i.test(pushUrl) || /^[^\s/@:]+@[^\s/:]+:.+/.test(pushUrl)
))) {
return "ssh_push_remote";
}
return null;
}
export function resolveAmbientHostCredentialCheckout(input: {
adapterCwd: string | null | undefined;
preferredProjectWorkspaceId: string | null | undefined;
projectWorkspaces: Array<{ id: string; cwd: string | null; repoUrl: string | null }>;
reusableExecutionWorkspace?: { cwd: string | null; repoUrl: string | null } | null;
}) {
const selectedProjectWorkspace = prioritizeProjectWorkspaceCandidatesForRun(
input.projectWorkspaces,
input.preferredProjectWorkspaceId,
)[0] ?? null;
const reusableCwd = readNonEmptyString(input.reusableExecutionWorkspace?.cwd);
const projectCwd = readNonEmptyString(selectedProjectWorkspace?.cwd);
const checkoutCwd = reusableCwd
?? (projectCwd === REPO_ONLY_CWD_SENTINEL ? null : projectCwd)
?? readNonEmptyString(input.adapterCwd);
const configuredPushUrl = readNonEmptyString(
input.reusableExecutionWorkspace?.repoUrl ?? selectedProjectWorkspace?.repoUrl,
);
return {
checkoutCwd,
configuredPushUrls: configuredPushUrl ? [configuredPushUrl] : [],
};
}
export function shouldProbeAmbientHostPushCredential(input: {
pushCapabilityPreflightRequired: boolean;
environmentDriver: string | null | undefined;
@@ -996,6 +1035,7 @@ export async function resolveExecutionRunAdapterConfig(input: {
ambientHostCredential?: {
enabled: boolean;
checkoutCwd?: string | null;
configuredPushUrls?: readonly string[];
probe?: typeof probeAmbientHostPushCredential;
};
};
@@ -1050,7 +1090,10 @@ export async function resolveExecutionRunAdapterConfig(input: {
&& requiredScopedEnvBinding.ambientHostCredential?.enabled
? await (
requiredScopedEnvBinding.ambientHostCredential.probe ?? probeAmbientHostPushCredential
)(requiredScopedEnvBinding.ambientHostCredential.checkoutCwd).catch(() => null)
)(
requiredScopedEnvBinding.ambientHostCredential.checkoutCwd,
{ configuredPushUrls: requiredScopedEnvBinding.ambientHostCredential.configuredPushUrls },
).catch(() => null)
: null;
let fallbackSecret: Awaited<ReturnType<RuntimeConfigSecretResolver["getByNameInsensitive"]>> | null = null;
let fallbackAuthorization: {
@@ -14794,6 +14837,33 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
environmentDriver: selectedEnvironmentForConfig?.driver ?? null,
trustPreset,
});
const ambientProjectWorkspaceRows = ambientHostPushCredentialEnabled
&& requestedExecutionWorkspaceMode !== "agent_default"
&& projectContext?.id
? await db
.select({
id: projectWorkspaces.id,
cwd: projectWorkspaces.cwd,
repoUrl: projectWorkspaces.repoUrl,
})
.from(projectWorkspaces)
.where(and(
eq(projectWorkspaces.companyId, agent.companyId),
eq(projectWorkspaces.projectId, projectContext.id),
))
.orderBy(asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id))
: [];
const ambientHostCredentialCheckout = resolveAmbientHostCredentialCheckout({
adapterCwd: executionRunConfig.cwd as string | null | undefined,
preferredProjectWorkspaceId: issueRef?.projectWorkspaceId,
projectWorkspaces: ambientProjectWorkspaceRows,
reusableExecutionWorkspace: reusableExistingExecutionWorkspace
? {
cwd: reusableExistingExecutionWorkspace.cwd,
repoUrl: reusableExistingExecutionWorkspace.repoUrl,
}
: null,
});
const { resolvedConfig, secretKeys, secretManifest } = await resolveExecutionRunAdapterConfig({
companyId: agent.companyId,
agentId: agent.id,
@@ -14823,9 +14893,8 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
fallbackInjectionEnvKey: "GH_TOKEN",
ambientHostCredential: {
enabled: ambientHostPushCredentialEnabled,
checkoutCwd:
reusableExistingExecutionWorkspace?.cwd
?? executionRunConfig.cwd as string | null | undefined,
checkoutCwd: ambientHostCredentialCheckout.checkoutCwd,
configuredPushUrls: ambientHostCredentialCheckout.configuredPushUrls,
},
}
: undefined,