From c043890ac058c341967bafc60a08d81f5254472c Mon Sep 17 00:00:00 2001 From: Towsty Date: Fri, 4 Sep 2026 08:20:39 -0500 Subject: [PATCH] Stop Comfy input folder filling with duplicate still uploads. Name uploads by content hash so the same still overwrites itself, enable stale input sweeps on purge, and remove the pile already on the desktop Shared input folder. Co-authored-by: Cursor --- server/utils/comfy.ts | 15 +++++++++++---- server/utils/imageComfy.ts | 13 +++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/server/utils/comfy.ts b/server/utils/comfy.ts index 38f371b..d95c2bf 100644 --- a/server/utils/comfy.ts +++ b/server/utils/comfy.ts @@ -1,4 +1,5 @@ import { LTX_NEGATIVE } from '~/utils/videoModels' +import { createHash } from 'node:crypto' import { comfyJobPrefix } from '~/utils/outputNames' let comfyHostOverride = '' @@ -124,7 +125,7 @@ export async function assertImageScaleToTotalPixelsNode() { export async function uploadImage(file: { filename: string; data: Buffer; type?: string }, jobId?: string) { const body = new FormData() const blob = new Blob([new Uint8Array(file.data)], { type: file.type || 'application/octet-stream' }) - const filename = comfyInputFilename(file.filename, jobId) + const filename = comfyInputFilename(file.filename, file.data, jobId) body.append('image', blob, filename) body.append('overwrite', 'true') body.append('type', 'input') @@ -140,12 +141,16 @@ export async function uploadImage(file: { filename: string; data: Buffer; type?: } } -export function comfyInputFilename(original: string, jobId?: string) { +/** Content-hash names so the same still overwrites itself (overwrite:true). Job id alone created hundreds of copies. */ +export function comfyInputFilename(original: string, data?: Buffer, jobId?: string) { const raw = String(original || 'still.png') const dot = raw.lastIndexOf('.') const ext = (dot >= 0 ? raw.slice(dot) : '.png').replace(/[^.a-zA-Z0-9]/g, '') || '.png' const base = (dot >= 0 ? raw.slice(0, dot) : raw).replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 48) || 'still' - return `${comfyJobPrefix(jobId)}_${base}${ext}` + const hash = data?.length + ? createHash('sha1').update(data).digest('hex').slice(0, 12) + : comfyJobPrefix(jobId) + return `aigen_${hash}_${base}${ext}` } function comfyQueueMessage(payload: unknown, status: number) { @@ -625,7 +630,9 @@ async function purgeOnDesktop(opts: { video: opts.video || null, audio: opts.audio || null, output: opts.output || (opts.video && /\.(png|jpe?g|webp|gif)$/i.test(opts.video.filename) ? opts.video : null), - sweep: false + // Sweep leftover studio uploads — exact delete alone left hundreds of copies when a job failed mid-way. + sweep: true, + sweepMaxAgeMs: 2 * 60 * 1000 }), signal: AbortSignal.timeout(12000) }) diff --git a/server/utils/imageComfy.ts b/server/utils/imageComfy.ts index a672a52..a194aef 100644 --- a/server/utils/imageComfy.ts +++ b/server/utils/imageComfy.ts @@ -1,3 +1,4 @@ +import { createHash } from 'node:crypto' import { AsyncLocalStorage } from 'node:async_hooks' import { getComfyHost, viaAgentFrontDoor } from '~/server/utils/comfy' import { comfyJobPrefix } from '~/utils/outputNames' @@ -94,18 +95,21 @@ export function imageComfyWsUrl(clientId: string) { return `${getImageComfyHost().replace(/^http/, 'ws')}/ws?clientId=${encodeURIComponent(clientId)}` } -function imageInputFilename(original: string, jobId?: string) { +function imageInputFilename(original: string, data?: Buffer, jobId?: string) { const raw = String(original || 'still.png') const dot = raw.lastIndexOf('.') const ext = (dot >= 0 ? raw.slice(dot) : '.png').replace(/[^.a-zA-Z0-9]/g, '') || '.png' const base = (dot >= 0 ? raw.slice(0, dot) : raw).replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 48) || 'still' - return `${comfyJobPrefix(jobId)}_${base}${ext}` + const hash = data?.length + ? createHash('sha1').update(data).digest('hex').slice(0, 12) + : comfyJobPrefix(jobId) + return `aigen_${hash}_${base}${ext}` } export async function uploadImageEdit(file: { filename: string; data: Buffer; type?: string }, jobId?: string) { const body = new FormData() const blob = new Blob([new Uint8Array(file.data)], { type: file.type || 'application/octet-stream' }) - const filename = imageInputFilename(file.filename, jobId) + const filename = imageInputFilename(file.filename, file.data, jobId) body.append('image', blob, filename) body.append('overwrite', 'true') body.append('type', 'input') @@ -393,7 +397,8 @@ async function purgeImageOnDesktop(opts: { imageNames, imageSubfolder: opts.inputSubfolder || '', output: opts.output || null, - sweep: false + sweep: true, + sweepMaxAgeMs: 2 * 60 * 1000 }), signal: AbortSignal.timeout(8000) })