diff --git a/packages/producer/src/services/distributed/plan.ts b/packages/producer/src/services/distributed/plan.ts index 9b980c8c4..ab2b7ae0f 100644 --- a/packages/producer/src/services/distributed/plan.ts +++ b/packages/producer/src/services/distributed/plan.ts @@ -839,7 +839,12 @@ export async function plan( } const workDir = join(planDir, ".plan-work"); - if (!existsSync(workDir)) mkdirSync(workDir, { recursive: true }); + // `.plan-work` is planner-owned scratch space. A prior attempt can fail + // before the end-of-plan cleanup and leave compiled assets behind; cpSync + // and compileStage both overlay their outputs, so reusing that directory + // would let stale bytes contaminate the new plan and its size check. + rmSync(workDir, { recursive: true, force: true }); + mkdirSync(workDir, { recursive: true }); const compiledDir = join(workDir, "compiled"); // Pre-seed the compiled directory with `projectDir`'s local assets diff --git a/packages/producer/src/services/distributed/planSizeCap.test.ts b/packages/producer/src/services/distributed/planSizeCap.test.ts index 677e7405b..b23edc5bd 100644 --- a/packages/producer/src/services/distributed/planSizeCap.test.ts +++ b/packages/producer/src/services/distributed/planSizeCap.test.ts @@ -320,6 +320,31 @@ describe("plan() early size budget", () => { }); describe("plan() reused directory size check", () => { + it("clears stale compiled scratch from a failed prior attempt", async () => { + const projectDir = mkdtempSync(join(runRoot, "project-reused-work-dir-")); + writeFileSync(join(projectDir, "index.html"), FIXTURE_HTML, "utf-8"); + const planDir = mkdtempSync(join(runRoot, "plandir-reused-work-dir-")); + const staleCompiledDir = join(planDir, ".plan-work", "compiled"); + mkdirSync(staleCompiledDir, { recursive: true }); + writeFileSync(join(staleCompiledDir, "stale-large-asset.bin"), Buffer.alloc(100_000)); + + const result = await plan( + projectDir, + { + fps: 30, + width: 320, + height: 240, + format: "mp4", + planDirSizeLimitBytes: 4_096, + }, + planDir, + ); + + expect(result.planHash).toMatch(/^[0-9a-f]{64}$/); + expect(existsSync(join(planDir, "compiled", "stale-large-asset.bin"))).toBe(false); + expect(measurePlanDirBytes(planDir)).toBeLessThan(4_096); + }, 30_000); + it("ignores stale freeze-owned metadata that the new plan overwrites", async () => { const projectDir = mkdtempSync(join(runRoot, "project-reused-plan-dir-")); writeFileSync(join(projectDir, "index.html"), FIXTURE_HTML, "utf-8");