DEV Community

Cover image for Why Running Your Asset Batch Twice Gets You Flagged, and the Script That Prevents It

Why Running Your Asset Batch Twice Gets You Flagged, and the Script That Prevents It

Last March I ran a batch of 60 sticker assets through my upload script, watched the progress bar finish, and went to bed. The next morning I re-ran the same command to "make sure it took." By noon I had 120 duplicate submissions sitting in a contributor queue, and within a week I had a review hold on my Adobe Stock account. That hold is the moment this whole post comes from.

Duplicate submissions are one of the few things stock platforms treat as a behavior problem, not a file problem. Rejected files come back with a reason code. Duplicates get your account looked at. Here is what I learned fixing it, and the small script that now makes it structurally impossible.

Why a re-run is worse than a mistake

When you upload a file twice, even weeks apart, the platforms fingerprint it. Adobe Stock checks visual similarity against your existing portfolio and flags near-identical submissions. Vecteezy and Dreamstime do the same thing on their side. The first duplicate usually gets rejected quietly. A pattern of them reads as keyword stuffing or portfolio flooding, and that is when review times stretch from days to weeks and rejections start citing "quality concerns" across files that are actually fine.

The trap is that re-running a batch feels safe. Nothing crashed, so what could go wrong? But the script does not know what happened yesterday. If your upload step is a loop over a folder, a re-run is a fresh loop over the same folder. Sixty files in, sixty files out, every time.

The fix is a ledger, not a smarter uploader

I did not need better upload code. I needed the script to remember what it had already done. That is what upload_tracker.py does, and it is deliberately boring: a plain text ledger with three commands.

python upload_tracker.py pending items.txt   # what is NOT done yet
python upload_tracker.py done "asset-001.jpg"
python upload_tracker.py status
Enter fullscreen mode Exit fullscreen mode

The upload step only ever consumes the output of pending. If a run dies halfway, the ledger knows which 37 of the 60 finished, and the next run picks up the last 23. There is no state in memory to lose, no database to corrupt, no lock file to clean up. It is standard library only, which matters when you are running this on a 1 vCPU, 2 GB box with no room for anything heavy.

One habit that made this stick: mark done immediately after the platform accepts the file, not after the whole batch finishes. A batch that dies at file 41 should leave 40 entries in the ledger, not zero.

The other half: metadata that does not get rejected on arrival

Duplicates are the flag you earn over time. Bad CSV headers are the rejection you earn in seconds. Each platform has its own exact column count and order, and the importer rejects anything else before it even looks at your art:

  • Adobe Stock: 5 columns
  • Vecteezy: 4 columns
  • Dreamstime: 15 columns

I used to keep three hand-edited spreadsheets and copy rows between them. That is where titles drifted and keyword counts went wrong. make_metadata.py writes the header each platform demands and nothing else, from one folder of files:

python make_metadata.py ./my-assets --platform adobe      --out metadata.csv --ext .jpg
python make_metadata.py ./my-assets --platform vecteezy   --out vecteezy.csv --ext .jpg
python make_metadata.py ./my-assets --platform dreamstime --out dreamstime.csv --ext .jpg
Enter fullscreen mode Exit fullscreen mode

Without a --titles file it title-cases the filename, which is fine for a first pass but replace it before you submit. Reviewers read titles.

How this fits a one-core server

The whole pipeline that produced my last 39 products (856 MB of finished output) ran on 1 vCPU and 2 GB RAM with no GPU. The tracker fits that budget because it does almost nothing. The habits that keep the heavy steps inside 2 GB are separate but worth repeating: one process at a time, gc.collect() after each asset, measure peak RSS with resource.getrusage(RUSAGE_SELF).ru_maxrss instead of guessing, and render previews at 60 dpi instead of 300 when you are only inspecting them.

The combination matters more than any single trick. A memory spike kills a batch halfway, and a batch that dies halfway is exactly the situation where someone re-runs it from the top. Stability and duplicate protection are the same problem.

What I would tell myself last March

The rule is simple: no upload step should ever see a file the ledger has not cleared. Build that one invariant and the duplicate flag stops being a risk you manage and becomes a thing your tooling prevents. My review hold cleared, my rejections dropped to normal levels, and I have not double-posted since, not because I got more careful, but because the script will not let me.

If you want the same four tools, make_metadata.py, upload_tracker.py, md2pdf.py, and pack_product.py, they are in the Pipeline Starter Kit, and the full method around them, from prompt to packaged product, is written up in the ai sticker factory playbook. It is the documentation of this exact pipeline, including the mistakes above.

Get ai sticker factory playbook


Building digital products? Free guides & kits for digital product sellers.

Top comments (0)