diagnostics: record native completion cancellation source

This commit is contained in:
Dotta committed 2026-10-10 20:37:39 -05:00
1 parent af10f6df09
commit 9b44e0db22
2 files changed
+71 -1

No files matched your search

@@ -10,7 +10,7 @@ import { createHeartbeatLifecycle, type HeartbeatLifecycleDependencies } from ".
import { createHeartbeatRunPreparation, ConfigurationIncompleteFailure } from "./run-preparation.js";
import { createHeartbeatRunState, getAdapterSessionCodec } from "./run-state.js";
import { buildEffectiveRunSessionConfigMetadata } from "./workspaces.js";
import { createHeartbeatRunCompletion, type CompleteHeartbeatRunInput, type FailHeartbeatRunInput, type HeartbeatRunCompletionDependencies } from "./run-completion.js";
import { createHeartbeatRunCompletion, nativeCompletionCancellationDiagnostic, type CompleteHeartbeatRunInput, type FailHeartbeatRunInput, type HeartbeatRunCompletionDependencies } from "./run-completion.js";
vi.mock("../live-events.js", () => ({ publishLiveEvent: vi.fn() }));
@@ -72,6 +72,45 @@ it("constructs completion handlers without querying or starting work", () => {
for (const callback of Object.values(deps)) if (vi.isMockFunction(callback)) expect(callback).not.toHaveBeenCalled();
});
describe("native completion cancellation diagnostics", () => {
it.each([
["Legacy controller lease lost", "legacy_controller_lease_lost"],
["Run stopped before provider startup", "stopped_before_provider_startup"],
["private-token-do-not-log", "other"],
])("classifies an aborted controller without exposing its message: %s", (message, abortReason) => {
const controller = new AbortController();
controller.abort(new Error(message));
const diagnostic = nativeCompletionCancellationDiagnostic({ outcome: "cancelled", nativeTerminal: "succeeded",
persistedRunStatus: "running", signal: controller.signal });
expect(diagnostic).toEqual({ nativeTerminal: "succeeded", outcomeSource: "execution_signal",
persistedRunStatus: "running", signalAborted: true, abortReason });
expect(JSON.stringify(diagnostic)).not.toContain(message);
});
it("reports a persisted cancellation ahead of an aborted controller", () => {
const controller = new AbortController();
controller.abort(new Error("Legacy controller lease lost"));
expect(nativeCompletionCancellationDiagnostic({ outcome: "cancelled", nativeTerminal: "succeeded",
persistedRunStatus: "cancelled", signal: controller.signal })).toMatchObject({
outcomeSource: "persisted_terminal", persistedRunStatus: "cancelled", abortReason: "legacy_controller_lease_lost",
});
});
it("reports persisted cancellation without inferring a signal cause", () => {
expect(nativeCompletionCancellationDiagnostic({ outcome: "cancelled", nativeTerminal: "succeeded",
persistedRunStatus: "cancelled", signal: new AbortController().signal })).toMatchObject({
outcomeSource: "persisted_terminal", signalAborted: false, abortReason: "not_aborted",
});
});
it("omits diagnostics for successful completion and provider cancellation", () => {
const signal = new AbortController().signal;
expect(nativeCompletionCancellationDiagnostic({ outcome: "succeeded", nativeTerminal: "succeeded", persistedRunStatus: "running", signal })).toBeNull();
expect(nativeCompletionCancellationDiagnostic({ outcome: "cancelled", nativeTerminal: "cancelled", persistedRunStatus: "running", signal })).toBeNull();
expect(nativeCompletionCancellationDiagnostic({ outcome: "cancelled", nativeTerminal: undefined, persistedRunStatus: "cancelled", signal })).toBeNull();
});
});
const support = await getEmbeddedPostgresTestSupport();
describe.skipIf(!support.supported)("heartbeat run completion boundary", () => {
let database: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
@@ -50,6 +50,30 @@ type RunState = ReturnType<typeof createHeartbeatRunState>;
type IssueContext = Awaited<ReturnType<ReturnType<typeof createHeartbeatRunPreparation>["getIssueExecutionContext"]>> | null;
type TaskSession = Awaited<ReturnType<RunState["getTaskSession"]>> | null;
/** Closed diagnostics for a host cancellation overriding a completed native turn. */
export function nativeCompletionCancellationDiagnostic(input: {
outcome: RunSessionOutcome;
nativeTerminal: string | undefined;
persistedRunStatus: string | null;
signal: AbortSignal;
}) {
if (input.outcome !== "cancelled" || input.nativeTerminal !== "succeeded") return null;
const persistedCancellation = input.persistedRunStatus === "cancelled";
if (!persistedCancellation && !input.signal.aborted) return null;
const abortMessage = input.signal.reason instanceof Error ? input.signal.reason.message : null;
return {
nativeTerminal: "succeeded",
outcomeSource: persistedCancellation ? "persisted_terminal" : "execution_signal",
persistedRunStatus: input.persistedRunStatus === null ? "missing"
: ["queued", "scheduled_retry", "running", "succeeded", "failed", "cancelled", "timed_out", "interrupted"].includes(input.persistedRunStatus)
? input.persistedRunStatus : "unknown",
signalAborted: input.signal.aborted,
abortReason: !input.signal.aborted ? "not_aborted"
: abortMessage === "Legacy controller lease lost" ? "legacy_controller_lease_lost"
: abortMessage === "Run stopped before provider startup" ? "stopped_before_provider_startup" : "other",
};
}
/** Completion persists terminal outcomes; the executor retains dispatch and cleanup ownership. */
export interface HeartbeatRunCompletionDependencies extends Pick<RunState,
"getRun" | "getAgent" | "resolveNormalizedUsageForSession" | "clearTaskSessions" | "upsertTaskSession">,
@@ -393,6 +417,12 @@ export function createHeartbeatRunCompletion(db: Db, dependencies: HeartbeatRunC
} else {
outcome = "failed";
}
const nativeCancellationDiagnostic = nativeCompletionCancellationDiagnostic({
outcome,
nativeTerminal: adapterResult.nativeFinalization?.terminal.runTerminalState,
persistedRunStatus: latestRun?.status ?? null,
signal,
});
const nextSessionState = resolveNextSessionState({
adapterType: agent.adapterType,
@@ -666,6 +696,7 @@ export function createHeartbeatRunCompletion(db: Db, dependencies: HeartbeatRunC
payload: {
status,
exitCode: adapterResult.exitCode,
...(nativeCancellationDiagnostic ? { nativeOutcomeOverride: nativeCancellationDiagnostic } : {}),
...(readRunCancellation(finalizedRun.resultJson) ? { cancellation: readRunCancellation(finalizedRun.resultJson) } : {}),
},
});