fix(intake): wake the worker when a draft is regenerated (#272)
Regenerate set state='queued', cleared attempts and ai_error, and answered 200 — then nothing ran the worker, so the row sat until the five-minute sweeper happened along. From the admin's side that is indistinguishable from a dead button, and the obvious response is to press it again. The submission path has kicked the worker since #223. Both paths put a row into 'queued'; only one asked for it to be drafted. That was an oversight in #225 rather than a decision: the kick was added to the submission path in a later task and the action routes were never revisited. Fixed in both places that re-queue — the admin route and the signed regenerate link from the notification email, which sets the identical state. Fire and forget with a logged catch, exactly as the submission path does: a slow or failing model call must not become a failed request for the admin, and the sweeper is still the backstop if the kick misses. The card still does not update itself once the draft lands, since drafting takes a few seconds and the screen has no way to know it finished. That is a UI question rather than this bug, and is noted on #272. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { Router, Request, Response } from 'express';
|
import { Router, Request, Response } from 'express';
|
||||||
import { pool } from '../db';
|
import { pool } from '../db';
|
||||||
import { asyncRoute } from '../asyncRoute';
|
import { asyncRoute } from '../asyncRoute';
|
||||||
|
import { draftQueued } from '../intake/draftingWorker';
|
||||||
import { nextPriceSource, PriceSource } from '../intake/priceSource';
|
import { nextPriceSource, PriceSource } from '../intake/priceSource';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@@ -148,6 +149,15 @@ router.post(
|
|||||||
[req.params.itemId]
|
[req.params.itemId]
|
||||||
);
|
);
|
||||||
if (rowCount === 0) return res.status(404).json({ error: 'no draft for this item' });
|
if (rowCount === 0) return res.status(404).json({ error: 'no draft for this item' });
|
||||||
|
|
||||||
|
// Wake the worker rather than leaving the row for the five-minute sweeper.
|
||||||
|
// Both this and the submission path put a row into 'queued'; only that one
|
||||||
|
// asked for it to be drafted, which made this button indistinguishable from
|
||||||
|
// a dead one (#272). Fire and forget with a logged catch, exactly as there:
|
||||||
|
// a slow or failing model call must not become a failed request for the
|
||||||
|
// admin, and the sweeper is still the backstop if this misses.
|
||||||
|
void draftQueued(1).catch((err) => console.error('[drafting] after regenerate:', err));
|
||||||
|
|
||||||
res.json({ state: 'queued' });
|
res.json({ state: 'queued' });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Router, Request, Response } from 'express';
|
import { Router, Request, Response } from 'express';
|
||||||
import { pool } from '../db';
|
import { pool } from '../db';
|
||||||
import { asyncRoute } from '../asyncRoute';
|
import { asyncRoute } from '../asyncRoute';
|
||||||
|
import { draftQueued } from '../intake/draftingWorker';
|
||||||
import { IntakeAction, verifyAction } from '../intake/actionLinks';
|
import { IntakeAction, verifyAction } from '../intake/actionLinks';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@@ -96,6 +97,15 @@ router.post(
|
|||||||
[checked.itemId]
|
[checked.itemId]
|
||||||
);
|
);
|
||||||
if (rowCount === 0) return res.status(404).json({ error: 'no draft for this item' });
|
if (rowCount === 0) return res.status(404).json({ error: 'no draft for this item' });
|
||||||
|
|
||||||
|
// Wake the worker rather than leaving the row for the five-minute sweeper.
|
||||||
|
// Both this and the submission path put a row into 'queued'; only that one
|
||||||
|
// asked for it to be drafted, which made this button indistinguishable from
|
||||||
|
// a dead one (#272). Fire and forget with a logged catch, exactly as there:
|
||||||
|
// a slow or failing model call must not become a failed request for the
|
||||||
|
// admin, and the sweeper is still the backstop if this misses.
|
||||||
|
void draftQueued(1).catch((err) => console.error('[drafting] after regenerate:', err));
|
||||||
|
|
||||||
return res.json({ state: 'queued' });
|
return res.json({ state: 'queued' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user