Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions apps/server/src/routes/turns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ export async function executeTurn(
...(partialOutput !== undefined ? { output: partialOutput } : {}),
error: {
code: result.code,
message: indeterminateMessage(result.code),
message: indeterminateMessage(result.code, partialOutput !== undefined),
retryable: result.code === "turn_timeout" || result.code === "turn_cancelled",
...(result.diagnostic !== "" ? { diagnostic: result.diagnostic } : {}),
},
Expand Down Expand Up @@ -585,16 +585,24 @@ export async function handleTurnCancel(
}

const INDETERMINATE_MESSAGES: Record<string, string> = {
turn_timeout: "the turn exceeded its time budget and was terminated; partial output was preserved",
turn_cancelled: "the turn was cancelled by the operator; partial output was preserved",
turn_engine_unavailable: "the engine process could not be started; check that the engine CLI is installed and reachable",
turn_protocol_invalid: "the engine output did not conform to the expected protocol; no automatic retry was attempted",
turn_driver_failure: "the driver encountered an internal error; no automatic retry was attempted",
turn_process_failed: "the engine process exited with a non-zero status; no automatic retry was attempted",
turn_process_exit_1: "the engine process exited with status 1; no automatic retry was attempted",
};

function indeterminateMessage(code: string): string {
function indeterminateMessage(code: string, hasPartialOutput: boolean): string {
if (code === "turn_timeout") {
return hasPartialOutput
? "the turn exceeded its time budget and was terminated; partial output was preserved"
: "the turn exceeded its time budget and was terminated before any output was received";
}
if (code === "turn_cancelled") {
return hasPartialOutput
? "the turn was cancelled by the operator; partial output was preserved"
: "the turn was cancelled by the operator before any output was received";
}
return INDETERMINATE_MESSAGES[code] ?? `the engine process ended unexpectedly (code: ${code}); no automatic retry was attempted`;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/server/test/turns-cancel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ test("POST /turns/cancel aborts the in-flight turn as indeterminate/turn_cancell
assert.equal(record.status, "indeterminate");
assert.deepEqual(record.error, {
code: "turn_cancelled",
message: "the turn was cancelled by the operator; partial output was preserved",
message: "the turn was cancelled by the operator before any output was received",
retryable: true,
});

Expand Down
24 changes: 24 additions & 0 deletions apps/server/test/turns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,30 @@ test("turn_timeout message names the timeout and preserved output (AC-003)", asy
}
});

test("turn_timeout does not claim to preserve output when no delta arrived", async () => {
const turnDriver = new FakeTurnDriver({
status: "indeterminate",
events: [{ type: "run.started", runId: "run-1", timestamp: "2026-08-24T00:00:00.000Z" }],
diagnostic: "",
code: "turn_timeout",
});
const server = await startTestServer(undefined, turnDriver);
const workspace = await copyExampleWorkspace();
try {
await openWorkspace(server.baseUrl, server.token, workspace);
const response = await api(server.baseUrl, "/turns", {
method: "POST",
token: server.token,
body: { positionId: "repo-owner", input: "hello", engine: "qoder" },
});
const record = response.body as { error: { message: string }; output?: string };
assert.match(record.error.message, /before any output was received/i);
assert.equal(record.output, undefined);
} finally {
await server.close();
}
});

test("diagnostic from engine stderr reaches the turn record (AC-004)", async () => {
const turnDriver = new FakeTurnDriver({
status: "indeterminate",
Expand Down
Loading