fix: harden finalize retry metadata

This commit is contained in:
Eva
2026-05-01 20:24:43 +07:00
committed by Josh Lehman
parent 7d8ffdcfe5
commit 683a2dec50
3 changed files with 39 additions and 1 deletions

View File

@@ -97,6 +97,41 @@ describe("before_agent_finalize hook runner", () => {
});
});
it("skips malformed retry instructions when merging revise decisions", async () => {
const runner = createHookRunner(
createMockPluginRegistry([
{
hookName: "before_agent_finalize",
handler: vi.fn().mockResolvedValue({
action: "revise",
reason: "malformed retry payload should not crash",
retry: { instruction: 123, idempotencyKey: "bad-retry" } as never,
}),
},
{
hookName: "before_agent_finalize",
handler: vi.fn().mockResolvedValue({
action: "revise",
reason: "valid retry still applies",
retry: {
instruction: " rerun the focused tests ",
idempotencyKey: "valid-retry",
},
}),
},
]),
);
await expect(runner.runBeforeAgentFinalize(EVENT, TEST_PLUGIN_AGENT_CTX)).resolves.toEqual({
action: "revise",
reason: "malformed retry payload should not crash\n\nvalid retry still applies",
retry: {
instruction: "rerun the focused tests",
idempotencyKey: "valid-retry",
},
});
});
it("lets finalize override earlier revise decisions", async () => {
const runner = createHookRunner(
createMockPluginRegistry([

View File

@@ -322,7 +322,7 @@ export function createHookRunner(
const normalizeRetry = (
retry: PluginHookBeforeAgentFinalizeResult["retry"] | undefined,
): PluginHookBeforeAgentFinalizeResult["retry"] | undefined => {
const instruction = retry?.instruction.trim();
const instruction = typeof retry?.instruction === "string" ? retry.instruction.trim() : "";
if (!instruction) {
return undefined;
}

View File

@@ -115,6 +115,9 @@ function waitForTerminalEventHandlers(params: {
}
let timeout: NodeJS.Timeout | undefined;
const settled = Promise.allSettled(pendingHandlers).then(() => "settled" as const);
// Promise.race bounds the host wait; JavaScript cannot cancel the plugin
// promises themselves, so timeout also marks the run expired to block late
// run-context resurrection by handlers that eventually settle.
const timedOut = new Promise<"timeout">((resolve) => {
timeout = setTimeout(() => {
markTerminalEventCleanupExpired(runId);