fix: verify lazy native provider launch during restored model selection

This commit is contained in:
Dotta 2026-09-11 14:25:28 -05:00
parent 6f65d00732
commit 06ab95bb3c
3 changed files with 47 additions and 5 deletions

View File

@ -642,6 +642,9 @@ fn response_error_classification(error: &ResponseError) -> &'static str {
_ => {}
}
match error.message.as_str() {
"ACPX provider spawned after ownership admission was sealed" => {
"provider_spawn_after_ownership_seal"
}
"ACPX recovery identity conflicts with the immutable session configuration" => {
"recovery_configuration_mismatch"
}

View File

@ -1311,6 +1311,36 @@ describe("Codex ACPX runtime adapter", () => {
});
});
it("verifies a lazy recovered provider spawned by model selection before returning", async () => {
const runtime = fakeRuntime();
const command = fakeCommand();
vi.mocked(command.spawn).mockReturnValue(fakeChild());
let runtimeOptions: AcpRuntimeOptions | undefined;
let acknowledgeOwnership!: () => void;
const ownership = new Promise<void>((resolve) => { acknowledgeOwnership = resolve; });
vi.mocked(runtime.setConfigOption!).mockImplementation(async () => {
await Promise.resolve();
runtimeOptions?.spawnAgent?.({ command: "ignored", args: ["--stdio"], options: {} });
});
const port = await openCodexAcpxRuntime(openOptions(command), {
createRegistry: () => registry(), createStore: () => store(),
awaitProviderOwnership: () => ownership,
awaitProviderExit: providerOwnershipEstablished,
createRuntime: (options) => { runtimeOptions = options; return runtime; },
});
let admitted = false;
const selection = port.setModel!("gpt-5.6-sol").then(() => { admitted = true; });
void selection.catch(() => undefined);
await vi.waitFor(() => expect(command.spawn).toHaveBeenCalledOnce());
expect(admitted).toBe(false);
acknowledgeOwnership();
await selection;
expect(admitted).toBe(true);
expect(() => runtimeOptions?.spawnAgent?.({ command: "ignored", args: [], options: {} }))
.toThrow("provider spawned after ownership admission was sealed");
await port.close({ reason: "test complete" });
});
it("admits a verified provider that starts with the first recovered turn", async () => {
const runtime = fakeRuntime();
const child = fakeChild();

View File

@ -1156,11 +1156,20 @@ function runtimePort(
...(runtime.setConfigOption
? {
async setModel(model: string) {
await runtime.setConfigOption?.({
handle,
key: "model",
value: model,
});
// A restored handle can be lazy: selecting the pinned model may
// launch its first provider before any prompt. Admit that spawn
// only for this control call, and verify ownership before return.
const finishOwnershipAdmission =
children.beginLifetimeOwnershipAdmission();
try {
await runtime.setConfigOption?.({
handle,
key: "model",
value: model,
});
} finally {
await finishOwnershipAdmission();
}
},
}
: {}),