fix(dn): run a queued agent job only on ticks with no inbox batch
Both are page reads; two in one tick can pass the 300s ceiling and get a batch reclaimed and read twice. The job waits for the next tick instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4c437cd320
commit
82c6f95878
2 changed files with 14 additions and 16 deletions
|
|
@ -695,36 +695,31 @@ describe("POST /api/dn-batches/tick — queued MCP jobs", () => {
|
|||
expect(optionsArg).toEqual({ limit: 1, deadlineMs: 120000 });
|
||||
});
|
||||
|
||||
it("drains after the inbox batches have run", async () => {
|
||||
const order: string[] = [];
|
||||
runBatchMock.mockImplementation(async () => {
|
||||
order.push("inbox");
|
||||
return { status: "processed", notesDetected: 1, notesPromoted: 1, checks: [] };
|
||||
});
|
||||
tickMcpJobsMock.mockImplementation(async () => {
|
||||
order.push("mcp");
|
||||
return { claimed: 1, completed: 1, failed: 0 };
|
||||
});
|
||||
|
||||
it("leaves the job for the next tick when an inbox batch already ran", async () => {
|
||||
// One unit of work per tick: an inbox read plus a batch job could pass the
|
||||
// 300s ceiling and get the batch reclaimed and read twice.
|
||||
await POST(postWithValidAuth());
|
||||
|
||||
expect(order).toEqual(["inbox", "mcp"]);
|
||||
expect(runBatchMock).toHaveBeenCalled();
|
||||
expect(tickMcpJobsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports the drain alongside the batch results so the n8n log shows it ran", async () => {
|
||||
it("reports the drain so the n8n log shows it ran", async () => {
|
||||
dnBatchesGetFullList.mockImplementation(async () => []);
|
||||
tickMcpJobsMock.mockResolvedValue({ claimed: 1, completed: 1, failed: 0 });
|
||||
|
||||
const body = await (await POST(postWithValidAuth())).json();
|
||||
|
||||
expect(body.mcpJobs).toEqual({ claimed: 1, completed: 1, failed: 0 });
|
||||
// The drain is not a batch: it must not inflate claimed or the results.
|
||||
expect(body.claimed).toBe(1);
|
||||
expect(body.results).toHaveLength(1);
|
||||
expect(body.claimed).toBe(0);
|
||||
expect(body.results).toHaveLength(0);
|
||||
});
|
||||
|
||||
// Without MINIMAX_API_KEY tickMcpJobs throws processing_setup_required,
|
||||
// and without the n8n secret this route is not the scheduler at all.
|
||||
it("still returns 200 when the drain throws", async () => {
|
||||
dnBatchesGetFullList.mockImplementation(async () => []);
|
||||
tickMcpJobsMock.mockRejectedValue(new Error("processing_setup_required"));
|
||||
|
||||
const res = await POST(postWithValidAuth());
|
||||
|
|
|
|||
|
|
@ -131,8 +131,11 @@ export async function POST(request: Request) {
|
|||
// Deferred, never fatal: a missing MINIMAX_API_KEY makes tickMcpJobs throw
|
||||
// processing_setup_required, and a drain failure must not turn a successful
|
||||
// inbox tick into an error for the operator.
|
||||
//
|
||||
// Only when no inbox batch ran in this tick: both are page reads, and two in
|
||||
// one tick can pass the 300s ceiling and get a batch reclaimed and re-read.
|
||||
let mcpJobs: { claimed: number; completed: number; failed: number } | undefined;
|
||||
try {
|
||||
if (results.length === 0) try {
|
||||
const principal = { id: "dn-tick", permissions: new Set<Permission>(["process"]) };
|
||||
mcpJobs = await tickMcpJobs(await createPurchasingContext(principal), { limit: 1, deadlineMs: 120000 });
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue