fix(daytona): bound one-shot exec requests by their command deadline (#19298)

Bound the Daytona one-shot executeCommand request at its command
deadline plus 5 s and return an attributed timed-out result, instead of
waiting for the host RPC deadline. Bound the stdin cleanup the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Devin FoleyandClaude Opus 5.5 authored and GitHub committed 2026-11-05 11:39:00 -08:00
1 parent 7f5d0793bd
commit ee72132a44
2 files changed
+96 -2

No files matched your search

@@ -0,0 +1,76 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const getSandbox = vi.hoisted(() => vi.fn());
vi.mock("@daytonaio/sdk", () => ({
Daytona: class { get = getSandbox; },
DaytonaNotFoundError: class extends Error {},
DaytonaTimeoutError: class extends Error {},
}));
import plugin, { __resetDaytonaSandboxHandleCacheForTest } from "./plugin.js";
function fixture() {
const process = { executeCommand: vi.fn() };
const fs = { uploadFile: vi.fn().mockResolvedValue(undefined), deleteFile: vi.fn().mockResolvedValue(undefined) };
getSandbox.mockResolvedValue({ id: "fixture-sandbox", state: "started", process, fs });
const execute = (stdin?: string) => plugin.definition.onEnvironmentExecute!({
driverKey: "daytona", companyId: "fixture-company", environmentId: "fixture-environment",
config: { apiKey: "fixture-not-a-key", timeoutMs: 300_000 }, bypassSession: true,
lease: { providerLeaseId: "fixture-sandbox", metadata: {} },
command: "/pack/bin/opencode", args: ["--version"], timeoutMs: 30_000,
...(stdin !== undefined ? { stdin } : {}),
});
return { process, fs, execute };
}
describe("Daytona one-shot exec request deadline", () => {
beforeEach(() => {
__resetDaytonaSandboxHandleCacheForTest();
getSandbox.mockReset();
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
__resetDaytonaSandboxHandleCacheForTest();
});
it("returns a timed-out result when the provider exec request outlives the command deadline", async () => {
const { process, execute } = fixture();
process.executeCommand.mockImplementation(() => new Promise(() => undefined));
let settled = false;
const pending = execute().finally(() => { settled = true; });
await vi.advanceTimersByTimeAsync(34_999);
expect(settled).toBe(false);
await vi.advanceTimersByTimeAsync(1);
expect(await pending).toMatchObject({
exitCode: null, timedOut: true,
metadata: { timeoutScope: "provider_exec_request", commandExitConfirmed: false },
});
expect(process.executeCommand).toHaveBeenCalledOnce();
});
it("keeps a slow exec that answers within the request deadline", async () => {
const { process, execute } = fixture();
process.executeCommand.mockImplementation(() => new Promise(resolve => setTimeout(
() => resolve({ exitCode: 0, result: "1.2.3", artifacts: { stdout: "1.2.3" } }), 34_000)));
const pending = execute();
await vi.advanceTimersByTimeAsync(34_000);
expect(await pending).toMatchObject({ exitCode: 0, timedOut: false, stdout: "1.2.3" });
});
it("bounds a stalled stdin cleanup after a timed-out exec request", async () => {
const { process, fs, execute } = fixture();
process.executeCommand.mockImplementation(() => new Promise(() => undefined));
fs.deleteFile.mockImplementation(() => new Promise(() => undefined));
let settled = false;
const pending = execute("input").finally(() => { settled = true; });
await vi.advanceTimersByTimeAsync(39_999);
expect(settled).toBe(false);
await vi.advanceTimersByTimeAsync(1);
expect(await pending).toMatchObject({
exitCode: null, timedOut: true,
metadata: { timeoutScope: "provider_exec_request", commandExitConfirmed: false },
});
expect(fs.deleteFile).toHaveBeenCalledOnce();
});
});
@@ -1941,7 +1941,11 @@ async function executeOneShot(
// attribute a step's exec time to the provider boundary through the
// free-form `metadata.durationMs`.
execStart = timingNow();
const result = await sandbox.process.executeCommand(command, undefined, undefined, timeoutSeconds);
const result = await withLivenessTimeout(
"process.executeCommand",
timeoutSeconds * 1_000 + LIVENESS_START_TIMEOUT_MARGIN_MS,
() => sandbox.process.executeCommand(command, undefined, undefined, timeoutSeconds),
);
const durationMs = timingNow() - execStart;
return {
@@ -1952,6 +1956,19 @@ async function executeOneShot(
metadata: { durationMs },
};
} catch (error) {
if (error instanceof SandboxLivenessTimeoutError && execStart != null) {
return {
exitCode: null,
timedOut: true,
stdout: "",
stderr: `${error.message}\n`,
metadata: {
durationMs: timingNow() - execStart,
timeoutScope: "provider_exec_request",
commandExitConfirmed: false,
},
};
}
if (error instanceof DaytonaTimeoutError) {
const timeoutMessage = gitNet
? `Git network operation timed out after ${Math.round(effectiveTimeoutMs / 1000)} s — the remote may be unreachable or noninteractive credentials are not configured.`
@@ -1972,7 +1989,8 @@ async function executeOneShot(
throw error;
} finally {
if (stdinPath) {
await sandbox.fs.deleteFile(stdinPath).catch(() => undefined);
await withLivenessTimeout("fs.deleteFile", LIVENESS_START_TIMEOUT_MARGIN_MS,
() => sandbox.fs.deleteFile(stdinPath)).catch(() => undefined);
}
}
}