fix(api): isolate the factory validation scratch dir from the worker's

The tool factory created its per-request validation scratch dir at tmpdir()/snapotter-scratch/<jobId> -- the same path the BullMQ worker uses for job processing (worker.ts scratchRoot()/<jobId>) whenever SCRATCH_PATH is unset. For sync tools this is harmless because the factory blocks on the worker before its finally cleanup runs. For "long" tools the factory returns 202 immediately and then rm's that dir, racing the worker that is writing the job's input there -- so tools that stage input in the worker scratch dir (epub-convert, to-epub) intermittently failed with ENOENT. Production never hit this because it sets SCRATCH_PATH, diverging the two paths. Give the factory's validation dir a -prep suffix so its cleanup can never touch the worker's job dir.
This commit is contained in:
SnapOtter
2026-06-17 14:28:41 +08:00
parent d46b82e11e
commit 7a205eeb12
+8 -2
View File
@@ -341,8 +341,14 @@ export function createToolRoute<T>(app: FastifyInstance, config: ToolRouteConfig
const toolMeta = TOOLS.find((t) => t.id === config.toolId);
const modality = toolMeta?.modality ?? "image";
// Per-request scratch dir for handlers that need temp files
const scratchDir = join(tmpdir(), "snapotter-scratch", jobId);
// Per-request scratch dir for input handlers that need temp files during
// validation. MUST stay distinct from the worker's job scratch dir
// (worker.ts scratchRoot()/<jobId>): for "long" tools the factory returns
// 202 below and the `finally` rm's this dir immediately, which would race
// and delete the worker's input mid-job whenever SCRATCH_PATH is unset
// (both otherwise default to tmpdir()/snapotter-scratch/<jobId>). The
// "-prep" suffix keeps the two from colliding.
const scratchDir = join(tmpdir(), "snapotter-scratch", `${jobId}-prep`);
await mkdir(scratchDir, { recursive: true });
try {
// Reject files whose extension is not in the tool's acceptedInputs.