fix(native): decide warm and adopted Runner process reuse from one key (#20005)

This commit is contained in:
Nicky Leach authored and GitHub committed 2026-11-08 01:02:50 -08:00
1 parent 1444b0d151
commit e7e870cfc4
5 files changed
+140 -66

No files matched your search

@@ -25,12 +25,16 @@ export const GENERATED_RUNTIME_ENVIRONMENT_KEYS: ReadonlySet<string> = new Set([
"PAPERCLIP_GITHUB_BROKER_TOKEN", "PAPERCLIP_GITHUB_BROKER_URL", "PAPERCLIP_GITHUB_BRIDGE_TOKEN", "PAPERCLIP_GITHUB_LAUNCHER_DIR",
]);
const RESERVED_KEYS = new Set([
"PATH", "HOME", "USERPROFILE", "CODEX_HOME", "AGENT_HOME", "SHELL", "NODE_OPTIONS", "NODE_PATH",
"PYTHONPATH", "PYTHONHOME", "RUBYOPT", "RUBYLIB", "PERL5OPT", "PERL5LIB", "BASH_ENV", "ENV", "ZDOTDIR", "GH_CONFIG_DIR",
export const PROVIDER_CREDENTIAL_ENVIRONMENT_KEYS: readonly string[] = [
"OPENAI_API_KEY", "CODEX_API_KEY", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN",
"AWS_BEARER_TOKEN_BEDROCK", "OPENROUTER_API_KEY", "XAI_API_KEY", "CURSOR_API_KEY", "CURSOR_AUTH_TOKEN",
"COPILOT_GITHUB_TOKEN", "KIMI_API_KEY", "GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GITHUB_ENTERPRISE_TOKEN", "PAPERCLIP_GIT_TOKEN",
];
const RESERVED_KEYS = new Set([
"PATH", "HOME", "USERPROFILE", "CODEX_HOME", "AGENT_HOME", "SHELL", "NODE_OPTIONS", "NODE_PATH",
"PYTHONPATH", "PYTHONHOME", "RUBYOPT", "RUBYLIB", "PERL5OPT", "PERL5LIB", "BASH_ENV", "ENV", "ZDOTDIR", "GH_CONFIG_DIR",
...PROVIDER_CREDENTIAL_ENVIRONMENT_KEYS,
]);
function eligibleKey(key: string): boolean {
@@ -12938,6 +12938,98 @@ describe("native warm session supervision", () => {
},
);
it.each([
{ name: "unchanged configuration", reuse: true },
{ name: "unchanged provider key", firstEnvironment: { OPENAI_API_KEY: "fixture-key" }, secondEnvironment: { OPENAI_API_KEY: "fixture-key" }, reuse: true },
{ name: "rotated provider key", firstEnvironment: { OPENAI_API_KEY: "fixture-key" }, secondEnvironment: { OPENAI_API_KEY: "fixture-rotated-key" }, reuse: false },
{ name: "rotated Claude token", firstEnvironment: { CLAUDE_CODE_OAUTH_TOKEN: "fixture-token" }, secondEnvironment: { CLAUDE_CODE_OAUTH_TOKEN: "fixture-rotated-token" }, reuse: false },
{ name: "tool refresh", refreshTools: true, reuse: false },
{ name: "network access change", firstEnvironment: { PAPERCLIP_RUNNER_NETWORK_ACCESS: "disabled" }, secondEnvironment: { PAPERCLIP_RUNNER_NETWORK_ACCESS: "enabled" }, reuse: false },
{ name: "run-scoped environment broker", firstEnvironment: { PAPERCLIP_GITHUB_BROKER_TOKEN: "first-run-capability" }, secondEnvironment: { PAPERCLIP_GITHUB_BROKER_TOKEN: "second-run-capability" }, reuse: false },
{ name: "Core-hosted GitHub access", managedGitHub: true, reuse: true, adoptionReuse: false },
] as Array<{ name: string; firstEnvironment?: Record<string, string>; secondEnvironment?: Record<string, string>;
refreshTools?: boolean; managedGitHub?: boolean; reuse: boolean; adoptionReuse?: boolean }>)(
"decides warm and adopted process reuse from one key: $name",
async ({ name, firstEnvironment = {}, secondEnvironment = {}, refreshTools, managedGitHub = false, reuse, adoptionReuse = reuse }) => {
const decisions: Record<string, boolean> = {};
for (const path of ["warm", "adoption"] as const) {
githubAccess.create.mockReset().mockImplementation(async () => ({ env: {}, ready: true, duplexRequested: false,
activate: githubAccess.activate.mockReset().mockImplementation(() => vi.fn()), stop: githubAccess.stop.mockReset().mockResolvedValue(undefined) }));
const stateBase = await mkdtemp(join(tmpdir(), "paperclip-runnerd-reuse-key-"));
const previousStateDirectory = process.env.PAPERCLIP_RUNNER_STATE_DIR;
const previousPaperclipHome = process.env.PAPERCLIP_HOME;
process.env.PAPERCLIP_RUNNER_STATE_DIR = stateBase;
process.env.PAPERCLIP_HOME = stateBase;
const scope = `${path}-${name.replaceAll(" ", "-")}`;
const first = {
...execution,
binding: { ...execution.binding, runId: `run-reuse-key-first-${scope}`, executionWorkspaceId: "workspace-reuse-key" },
workspace: { cwd: "/tmp/runnerd-reuse-key", repoUrl: null, repoRef: null, branchName: null },
session: { normalizedSessionId: `session-reuse-key-${scope}`, driverKind: "codex_app_server" as const,
protocolVersion: 1 as const, lifecyclePolicy: { mode: "warm" as const, idleTimeoutMs: 60_000 } },
} as NativeExecutionInputV1;
const second = { ...first, binding: { ...first.binding, runId: `run-reuse-key-second-${scope}` } } as NativeExecutionInputV1;
const target = { kind: "remote" as const, transport: "sandbox" as const, environmentId: "environment-reuse-key",
remoteCwd: "/home/daytona/paperclip-workspace", runner: { execute: vi.fn() } } as never;
const firstSession = { close: vi.fn(async () => undefined) };
const result = { result: { summary: "completed" }, terminal: { runTerminalState: "succeeded" }, turnId: "turn",
normalizedSessionId: first.session.normalizedSessionId, providerSessionId: "provider-reuse-key", driverKind: "test",
driverVersion: "1", nativeEventCount: 1, highestContiguousSourceSeq: 1, usage: null };
state.execute.mockReset()
.mockImplementationOnce(async (options) => {
await options.onCheckpoint?.({
identity: { runId: first.binding.runId, sessionId: first.session.normalizedSessionId,
companyId: first.binding.companyId, issueId: first.binding.issueId, agentId: first.binding.agentId },
providerSessionId: "provider-reuse-key", activeTurnId: "provider-turn-reuse-key",
semanticResult: { summary: "first" }, terminal: { runTerminalState: "succeeded" },
});
options.onSession?.(firstSession);
return result;
})
.mockImplementationOnce(async (options) => {
decisions[path] = options.existingSession === firstSession;
return result;
});
const inspected = vi.spyOn(remoteWarmRecoveryRuntime, "inspectRemoteWarmSessionRecovery").mockImplementation(async request => {
decisions[path] = request.canReuseProcess(first);
throw new Error("fixture adoption decision recorded");
});
try {
await executePaperclipNativeSession({ db: leaseDb(first), execution: first, runnerInstanceId: "runner-reuse-key",
useRunnerd: true, managedGitHub, runnerExecutionTarget: target,
runnerEnvironment: { PAPERCLIP_GITHUB_AUTH_MODE: "managed", ...firstEnvironment } });
const durableRoot = (await readdir(stateBase, { withFileTypes: true }))
.filter(entry => entry.isDirectory() && /^[a-f0-9]{64}$/.test(entry.name)).map(entry => join(stateBase, entry.name))[0]!;
const durableIdentity = { runId: first.binding.runId, normalizedSessionId: first.session.normalizedSessionId,
runnerInstanceId: "runner-reuse-key", environmentLeaseId: first.binding.executionWorkspaceId };
await mkdir(join(durableRoot, "control-plane"), { recursive: true });
await writeFile(join(durableRoot, "control-plane", "control-plane-state.json"), JSON.stringify(durableControlPlaneState(durableIdentity)));
await mkdir(join(durableRoot, "runner"), { recursive: true });
await writeFile(join(durableRoot, "runner", "runner-state.json"), JSON.stringify(durableRunnerState(durableIdentity, "ready")));
if (path === "adoption") expect(await closeIdleWarmNativeSessionsForRestart()).toEqual({ closed: 1, busy: 0, failed: 0 });
const continuation = executePaperclipNativeSession({
db: { ...leaseDb(second), select: () => ({ from: () => ({ where: () => ({ limit: () =>
Promise.resolve([{ status: "succeeded", runnerProfileJson: { nativeExecutionInput: first } }]) }) }) }) } as unknown as Db,
execution: second, runnerInstanceId: "runner-reuse-key", useRunnerd: true, managedGitHub, refreshTools,
runnerExecutionTarget: target, runnerEnvironment: { PAPERCLIP_GITHUB_AUTH_MODE: "managed", ...secondEnvironment } });
if (path === "adoption") await expect(continuation).rejects.toThrow("fixture adoption decision recorded");
else await continuation;
expect(decisions[path]).toBe(path === "adoption" ? adoptionReuse : reuse);
if (path === "warm") expect(firstSession.close).toHaveBeenCalledTimes(reuse ? 0 : 1);
} finally {
inspected.mockRestore();
await closeIdleWarmNativeSessionsForRestart();
if (previousStateDirectory === undefined) delete process.env.PAPERCLIP_RUNNER_STATE_DIR;
else process.env.PAPERCLIP_RUNNER_STATE_DIR = previousStateDirectory;
if (previousPaperclipHome === undefined) delete process.env.PAPERCLIP_HOME;
else process.env.PAPERCLIP_HOME = previousPaperclipHome;
await rm(stateBase, { recursive: true, force: true });
}
}
expect(decisions).toEqual({ warm: reuse, adoption: adoptionReuse });
},
);
it("does not replace a different company's warm session with the same normalized id", async () => {
const firstClose = vi.fn(async () => undefined);
const secondClose = vi.fn(async () => undefined);
@@ -68,7 +68,7 @@ import {
isKnownNativeSessionRootEntry,
} from "./native-session-root-entries.js";
import { createRunPreparationTiming } from "../execution-preparation-timing.js";
import { configuredEnvironment } from "../../vendor/paperclip-runner/index.js";
import { configuredEnvironment, PROVIDER_CREDENTIAL_ENVIRONMENT_KEYS } from "../../vendor/paperclip-runner/index.js";
import { parseProviderPerformanceSpans } from "../../vendor/paperclip-runner/index.js";
import { apiKeyBillingType } from "@paperclipai/adapter-utils/api-billing-type";
import { agents } from "@paperclipai/db";
@@ -122,7 +122,7 @@ import {
boundedExecutionCleanup,
EXECUTION_CONTROL_DEADLINE_MS,
} from "../execution-control-deadline.js";
import { createHash, randomUUID } from "node:crypto";
import { createHash, createHmac, randomUUID } from "node:crypto";
import { execFile, execFileSync } from "node:child_process";
import {
chmodSync,
@@ -241,7 +241,7 @@ import { assertNativeRetainedSandboxBinding, deferNativeControllerRecovery, Nati
import { redactRunnerApiPayloads } from "./runner-api-log-redaction.js";
import { appendHeartbeatRunEvent } from "../heartbeat-run-events.js";
import { appendNativeCredentialUpdate } from "./native-credential-updates.js";
import { nativeSha256 } from "./canonical.js";
import { canonicalNativeJson, nativeSha256 } from "./canonical.js";
import { lockSourceRecoveryCoordinators } from "./native-source-recovery-ownership.js";
import { PaperclipRunnerToolAuthority } from "./paperclip-runner-tool-authority.js";
import { createAssignedMcpTools, getAssignedMcpGateway } from "./assigned-mcp-tools.js";
@@ -669,22 +669,15 @@ type WarmNativeSession = {
runId: string;
issueId: string;
instructionWorkingCopy?: NativeInstructionWorkingCopy;
// Keep the admitted physical root independent of the per-run collection
// capability, which instruction preparation adopts before final admission.
instructionRoot?: string;
instructionPreparationRunId?: string;
agentId: string;
instructionCopy?: { runId: string; root?: string; targetIdentity: string; collectStopped: () => Promise<void> };
preparingRunId?: string;
managedAiCredentialIdentity?: string;
credentialRunId?: string;
githubAccess?: NativeGitHubAccess;
githubAuthenticationMode?: string;
networkAccess: boolean;
session: NativeSession;
ownerToken: symbol;
configDigest: string;
configuredEnvironmentDigest: string;
processConfigDigest?: string;
ownerScope: string;
normalizedSessionId: string;
companyId: string;
@@ -7605,17 +7598,29 @@ function needsCoreGitHubAccess(input: Pick<Parameters<typeof executePaperclipNat
return Boolean(input.managedGitHub && !(target?.kind === "remote" && target.transport === "sandbox" && target.runnerTask));
}
function warmNativeProcessConfigDigest(input: Parameters<typeof executePaperclipNativeSession>[0]): string | undefined {
if (needsCoreGitHubAccess(input) || input.runnerEnvironment?.PAPERCLIP_GITHUB_BROKER_TOKEN || input.instructionWorkingCopy) return undefined;
function nativeProcessReuseKey(input: Parameters<typeof executePaperclipNativeSession>[0]): string | undefined {
const environment = input.runnerEnvironment;
if (!input.managedGitHub && environment?.PAPERCLIP_GITHUB_BROKER_TOKEN) return undefined;
const schema = "paperclip.native-process-reuse.v2";
const providerCredentials = PROVIDER_CREDENTIAL_ENVIRONMENT_KEYS.flatMap(key => environment?.[key] ? [[key, environment[key]]] : []);
return nativeSha256({
environment: configuredEnvironment(input.runnerEnvironment),
schema,
environment: configuredEnvironment(environment),
providerCredentials: createHmac("sha256", `${schema}:${nativeSessionScopeKey(input.execution)}`)
.update(canonicalNativeJson(providerCredentials)).digest("hex"),
managedAiCredentialIdentity: input.managedAiCredentialIdentity ?? null,
observabilityCompatibilityKey: input.observability?.compatibilityKey ?? null,
githubAuthenticationMode: input.runnerEnvironment?.PAPERCLIP_GITHUB_AUTH_MODE ?? null,
networkAccess: input.runnerEnvironment?.PAPERCLIP_RUNNER_NETWORK_ACCESS === "enabled",
githubAuthenticationMode: environment?.PAPERCLIP_GITHUB_AUTH_MODE ?? null,
networkAccess: environment?.PAPERCLIP_RUNNER_NETWORK_ACCESS === "enabled",
coreGitHubAccess: needsCoreGitHubAccess(input),
instructionRoot: input.instructionWorkingCopy?.root ?? null,
});
}
function canReuseNativeProcess(input: Parameters<typeof executePaperclipNativeSession>[0], processConfigDigest: unknown): boolean {
return input.refreshTools !== true && typeof processConfigDigest === "string" && processConfigDigest === nativeProcessReuseKey(input);
}
function loadWarmNativeCheckpoint(
execution: NativeExecutionInput,
configDigest: string,
@@ -9631,13 +9636,9 @@ async function executePaperclipNativeSessionWithinScope(
}
return loadWarmNativeCheckpoint(previous, nativeSessionConfigDigest(previous, "remote"), "remote");
},
canReuseProcess: previous => {
const expected = warmNativeProcessConfigDigest(input);
if (!expected) return false;
const envelope = record(JSON.parse(readBoundedNativeFile(nativeSessionCheckpointPath(previous),
NATIVE_WARM_CHECKPOINT_MAX_BYTES, "native_session_supervisor_checkpoint_too_large").toString("utf8")));
return envelope.processConfigDigest === expected;
},
canReuseProcess: previous => !needsCoreGitHubAccess(input) && canReuseNativeProcess(input,
record(JSON.parse(readBoundedNativeFile(nativeSessionCheckpointPath(previous),
NATIVE_WARM_CHECKPOINT_MAX_BYTES, "native_session_supervisor_checkpoint_too_large").toString("utf8"))).processConfigDigest),
});
remoteWarmRecovery.releasedSource = releasedSource;
}
@@ -10192,7 +10193,7 @@ async function executePaperclipNativeSessionWithinScope(
};
const controllerCompatibility = {
configDigest: nativeSessionConfigDigest(input.execution, input.runnerExecutionTarget?.kind ?? "local"),
processConfigDigest: warmNativeProcessConfigDigest(input) ?? null,
processConfigDigest: nativeProcessReuseKey(input) ?? null,
};
const controlPlane = new PaperclipControlPlanePort(
input.db,
@@ -10539,9 +10540,6 @@ async function executePaperclipNativeSessionWithinScope(
lifecyclePolicy.mode === "warm"
? controllerCompatibility.configDigest
: null;
const configuredEnvironmentDigest = createHash("sha256")
.update(JSON.stringify(configuredEnvironment(input.runnerEnvironment)))
.digest("hex");
const warmSessionOwnerToken = Symbol(
`native-warm-session:${input.execution.binding.runId}`,
);
@@ -10592,34 +10590,15 @@ async function executePaperclipNativeSessionWithinScope(
}
}
if (entry) {
// Old run-scoped environments still require process replacement. A
// session-owned broker can change run authority without replacing it.
const hasBrokerCapability = Boolean(
!input.managedGitHub && input.runnerEnvironment?.PAPERCLIP_GITHUB_BROKER_TOKEN,
);
const credentialRunChanged =
Boolean(entry.credentialRunId) !== hasBrokerCapability ||
(hasBrokerCapability &&
entry.credentialRunId !== input.execution.binding.runId);
if (
(entry.pluginTaskId !== undefined && (input.runnerExecutionTarget?.kind !== "remote" || input.runnerExecutionTarget.transport !== "sandbox" ||
entry.pluginTaskId !== input.runnerExecutionTarget.sandboxLeaseAcquisition?.providerLeaseId)) ||
input.refreshTools === true ||
entry.closeOnReleaseReason !== undefined ||
entry.configDigest !== warmConfigDigest ||
entry.observabilityCompatibilityKey !== input.observability?.compatibilityKey ||
entry.configuredEnvironmentDigest !== configuredEnvironmentDigest ||
entry.instructionRoot !== input.instructionWorkingCopy?.root ||
entry.managedAiCredentialIdentity !== input.managedAiCredentialIdentity ||
credentialRunChanged ||
!canReuseNativeProcess(input, entry.processConfigDigest) ||
Boolean(entry.githubAccess) !== needsCoreGitHubAccess(input) ||
entry.githubAccess?.ready === false ||
(entry.githubAccess !== undefined && entry.githubAccess.duplexRequested !== adapterExecutionTargetEnablesSandboxDuplexBridge(input.runnerExecutionTarget)) ||
entry.githubAuthenticationMode !==
input.runnerEnvironment?.PAPERCLIP_GITHUB_AUTH_MODE ||
entry.networkAccess !==
(input.runnerEnvironment?.PAPERCLIP_RUNNER_NETWORK_ACCESS ===
"enabled")
(entry.githubAccess !== undefined && entry.githubAccess.duplexRequested !== adapterExecutionTargetEnablesSandboxDuplexBridge(input.runnerExecutionTarget))
) {
await finishStartupContextForFallback();
pluginTaskDescription=undefined;
@@ -11153,24 +11132,13 @@ async function executePaperclipNativeSessionWithinScope(
runId: input.execution.binding.runId,
issueId: input.execution.binding.issueId,
instructionWorkingCopy: input.instructionWorkingCopy?.checkpointWarm ? undefined : input.instructionWorkingCopy,
instructionRoot: input.instructionWorkingCopy?.root,
agentId: input.execution.binding.agentId,
managedAiCredentialIdentity: input.managedAiCredentialIdentity,
githubAuthenticationMode:
input.runnerEnvironment?.PAPERCLIP_GITHUB_AUTH_MODE,
networkAccess:
input.runnerEnvironment
?.PAPERCLIP_RUNNER_NETWORK_ACCESS === "enabled",
githubAccess,
credentialRunId: !input.managedGitHub && input.runnerEnvironment
?.PAPERCLIP_GITHUB_BROKER_TOKEN
? input.execution.binding.runId
: undefined,
session,
ownerToken: warmSessionOwnerToken,
configDigest: warmConfigDigest,
processConfigDigest: controllerCompatibility.processConfigDigest ?? undefined,
observabilityCompatibilityKey: input.observability?.compatibilityKey,
configuredEnvironmentDigest,
ownerScope: nativeSessionOwnerScope(
input.execution, input.runnerExecutionTarget?.environmentId ?? null,
),
@@ -41,9 +41,12 @@ const parkId = (source: DurableRecoveryIdentity) => `park_${sha(JSON.stringify(O
describeEmbeddedPostgres("plugin Runner release/resume through the Core execution entry", () => {
const environment = nativeRestartFixture();
const nativeIt = process.platform === "win32" ? it.skip : realProcessIt;
nativeIt.for([...(["codex", "claude"] as const).flatMap(harness => (["cached", "adopted"] as const).map(mode => [harness, mode] as const)), ["codex", "adopted cancellation"] as const, ["codex", "adopted legacy cancellation"] as const])("completes %s runs through a retained %s process", { timeout: 60_000 }, async ([harness, mode], context) => {
nativeIt.for([...(["codex", "claude"] as const).flatMap(harness => (["cached", "adopted"] as const).map(mode => [harness, mode] as const)), ["codex", "adopted cancellation"] as const, ["codex", "adopted legacy cancellation"] as const,
["codex", "cached managed GitHub"] as const, ["codex", "adopted managed GitHub"] as const])("completes %s runs through a retained %s process", { timeout: 60_000 }, async ([harness, mode], context) => {
if (harness === "claude" && !existsSync(fakeAcpxSidecar)) context.skip();
const adopted = mode.startsWith("adopted"), cancellation = mode.includes("cancellation"), qualifiedCancellation = mode === "adopted cancellation";
const managedGitHub = mode.endsWith("managed GitHub"), brokerUrl = "https://core.example.test/api", brokerToken = (runId: string) => `fixture-broker-${runId}`;
const preparedBrokers: Array<{ runId: string; token?: string }> = [];
const first = await environment.seedRun(`RELEASE-ENTRY-${harness}-${mode}`);
const { db, runId, issueId, native } = first;
const { companyId, agentId, runtimeRoot } = environment.current;
@@ -130,9 +133,12 @@ describeEmbeddedPostgres("plugin Runner release/resume through the Core executio
const task = async (raw: unknown, authorityRunId: string): Promise<unknown> => {
const operation = environmentTaskOperationSchema.parse(raw);
if (operation.kind === "describe") return { kind: "description", taskId, launchPreparationVersion: 1,
projectAuthCheckVersion: 1, warmTaskVersion: 1, releaseResumeVersion: 1, runner: artifact, ...(qualifiedCancellation ? { executionContextVersion: 1 } : {}) };
projectAuthCheckVersion: 1, warmTaskVersion: 1, releaseResumeVersion: 1, runner: artifact, ...(qualifiedCancellation ? { executionContextVersion: 1 } : {}),
...(managedGitHub ? { githubCredentialBrokerVersion: 1 } : {}) };
if (operation.kind === "prepare") {
expect(operation.requirements.identity.runId).toBe(authorityRunId);
expect(operation.githubBroker).toEqual(managedGitHub ? { url: brokerUrl, token: brokerToken(authorityRunId) } : undefined);
preparedBrokers.push({ runId: authorityRunId, token: operation.githubBroker?.token });
expect(operation.accountIdentity).toBe("fixture-account");
if (operation.priorIdentity) expect(sameSource(operation.priorIdentity, await state())).toBe(true);
expect(operation.requirements.settings.managedProfile).toBeUndefined();
@@ -204,7 +210,9 @@ describeEmbeddedPostgres("plugin Runner release/resume through the Core executio
await db.update(heartbeatRuns).set({ runnerProfileJson: { nativeExecutionInput: current }, runnerInstanceId: native.runnerInstanceId, completionContractId: contract!.id, completionContractSha256: contract!.canonicalSha256, nativeSessionId: current.session.normalizedSessionId })
.where(eq(heartbeatRuns.id, current.binding.runId));
return executePaperclipNativeSession({ db, execution: current, runnerInstanceId: native.runnerInstanceId, useRunnerd: true, runnerIngressAuthorized: true,
runnerEnvironment: harness === "claude" ? { ANTHROPIC_API_KEY: "synthetic-fixture" } : {},
runnerEnvironment: { ...(harness === "claude" ? { ANTHROPIC_API_KEY: "synthetic-fixture" } : {}),
...(managedGitHub ? { PAPERCLIP_GITHUB_BROKER_URL: brokerUrl, PAPERCLIP_GITHUB_BROKER_TOKEN: brokerToken(current.binding.runId) } : {}) },
managedGitHub,
managedAiCredentialHome: home, managedAiCredentialIdentity: "fixture-revision", managedAiAccountIdentity: "fixture-account", turnTimeoutMs: 15_000,
runnerExecutionTarget: { kind: "remote", transport: "sandbox", remoteCwd: workspace, environmentId, leaseId, providerKey: "plugin:fixture:tasks", runnerTask: operation => task(operation, current.binding.runId),
sandboxLeaseAcquisition: { outcome: leaseId === leaseA ? "created" : "resumed", providerLeaseId: taskId } } });
@@ -283,6 +291,7 @@ describeEmbeddedPostgres("plugin Runner release/resume through the Core executio
}
expect(claims).toEqual(releases.slice(0, -1)); expect(releases[1]!.runId).toBe(runB);
expect(releases[1]!.environmentLeaseId).toBe(leaseA); expect(handle!.child.pid).toBe(pid); expect(submissions).toBe(1);
expect([...new Set(preparedBrokers.map(prepared => prepared.runId))]).toEqual(releases.map(release => release.runId));
const methods = (await readFile(calls, "utf8")).trim().split(/\r?\n/);
if (harness === "codex") {
expect(methods.filter(method => method === "process-start")).toHaveLength(1);
+1
View File
@@ -218,6 +218,7 @@ export const CONFIGURED_ENVIRONMENT_KEYS = runner.CONFIGURED_ENVIRONMENT_KEYS;
export const GENERATED_RUNTIME_ENVIRONMENT_KEYS = runner.GENERATED_RUNTIME_ENVIRONMENT_KEYS;
export const configuredEnvironmentProjection = runner.configuredEnvironmentProjection;
export const configuredEnvironment = runner.configuredEnvironment;
export const PROVIDER_CREDENTIAL_ENVIRONMENT_KEYS = runner.PROVIDER_CREDENTIAL_ENVIRONMENT_KEYS;
export const probeNativeRunnerEnvironment = runner.probeNativeRunnerEnvironment;