Shipping Prism Organizer: How Scheduling My Own Cleanup Tool Found Three Bugs in One Afternoon
I wanted one thing this week: my Downloads folder and my Creative Cloud Files desktop to clean themselves up twice a month without me touching a keyboard. Instead I spent an afternoon debugging my own CLI tool live, in production, against my own laptop — and came out the other side with three real bug fixes and something I'm finally comfortable calling done: Prism Organizer is officially launching.
TL;DR
- I'm open-sourcing Prism Organizer, a portable Python CLI + TUI for scanning, sorting, deduping, and cleaning up Windows folders — dry-run by default, nothing ever hard-deleted, everything undoable.
- Trying to wire it into Windows Task Scheduler for my own twice-a-month cleanup surfaced three real bugs: a broken local Python install, an
argparsenaming collision that silently misrouted a whole subcommand, and a Windowsschtasksquirk that only accepts one day-of-month at a time. - All three are fixed and shipped as of
v1.3.2. The tool now genuinely runs itself, unattended, on the 1st and 15th of every month. - Full source,
CONTRIBUTING.md, and a realSECURITY.mdare up on GitHub if you want to poke at it.
Why I Built This in the First Place
Between running Syntaxure Labs, coursework at ISUFST, and SineAI Guild stuff, my Downloads folder had become a crime scene — installers next to screenshots next to half-finished PSDs next to who-knows-what .crdownload files. I didn't want a one-off cleanup, I wanted something that ran itself: scan, sort into sane category folders, flag junk instead of nuking it, and let me undo anything if it got something wrong. So I built Prism Organizer — a CLI first, then a full arrow-key TUI dashboard on top, because typing --recursive --no-interactive at 1am is not a personality trait I want.
By this week it had scan, sort-by-type/date, three-phase duplicate detection (size → partial hash → SHA-256, plus perceptual hashing for near-duplicate images), a custom YAML rules engine, optional AI classification (OpenAI, Ollama, or LM Studio — your choice, local-first if you want it), and a watch mode. What it didn't have, in any way I'd actually tested end-to-end, was the one feature that made all of that automatic: schedule add.
Bug #1: The Python That Wasn't There
First attempt at registering the schedule, python -m prism_organizer schedule add ..., threw a native Windows dialog: python314.dll was not found. Not a bug in my code — a half-broken Python 3.14 install shadowing a perfectly good Python 3.13 sitting one directory over. py -0p showed both; py -3.13 fixed the immediate problem in about ten seconds. Lesson filed under "always check where python before blaming your own code," and moving on.
Bug #2: The --command That Ate Itself
The second failure was more interesting, and entirely mine. I'd typed:
prism-organizer schedule add "C:\Users\...\Downloads" --command sort --interval monthly --days 1,15 --at 09:00
and got a stack trace from cmd_sort — a function that shouldn't have run at all, crashing on AttributeError: 'Namespace' object has no attribute 'by'.
The root cause was an argparse collision I'd shipped without noticing: my top-level subcommand parser used dest="command" (so args.command holds "scan", "sort", "schedule", etc.), and the schedule add subparser also defined a --command flag for "which action should this scheduled task run" — writing to that exact same args.command attribute. Parse schedule add ... --command sort and the nested flag silently overwrites the top-level one. By the time main() did commands.get(args.command), it was dispatching to cmd_sort with a Namespace that had never seen --by. Cloud-drive detection even kicked in as a side effect, since that's cmd_sort's job, not schedule's — which made the failure look far stranger than it was until I traced the actual attribute write order.
The fix: give the nested flag its own dest="run_command" so it stops colliding, update cmd_schedule() to read that instead, and add a regression test that literally asserts args.command == "schedule" after parsing schedule add. [confirm exact version/commit tag for this fix — v1.3.1] This is the kind of bug that only shows up when you actually run the command end-to-end instead of unit-testing each parser branch in isolation — which is exactly why it survived undetected through the initial release.
Bug #3: schtasks Doesn't Do Lists
With dispatch fixed, the tool correctly reached real Windows Task Scheduler — and Windows itself rejected the request: ERROR: Invalid value for /D option. I'd assumed schtasks /Create /SC MONTHLY /D 1,15 would register a single task firing on both days, the way you might expect from cron-style day lists. It doesn't. /D under /SC MONTHLY accepts exactly one numeric day. I confirmed it the boring, reliable way — ran the raw command by hand with a single day first, watched it succeed, then knew exactly what to build.
The real fix: TaskScheduler.add_task() now loops over each requested day and registers one schtasks entry per day — Prism Organizer - sort Downloads (day 1), (day 15) — with partial-failure handling so one bad day doesn't sink the whole batch, plus a group_base_name() helper so schedule remove can still treat them as one logical group instead of two mystery entries. Six new tests, full suite green at 45, shipped as v1.3.2.
What I'd Do Differently
Honestly, I'd have caught bug #2 in about thirty seconds if I'd run schedule add against a real Windows Task Scheduler once before calling the feature "documented and done" in the README. It's a classic trap: the individual pieces — argument parsing, task creation, task listing — all had unit coverage, but nothing exercised the actual dispatch path end-to-end with real subcommand strings. I've since made "run the exact command from the README, on a real machine, before merging" part of how I sign off on CLI features, not just an afterthought.
I also went back through the rest of the codebase with the same skeptical eye while I was in there, since I was about to make this public — and found one more real issue worth fixing before launch: the AI classification feature builds a destination folder path directly from whatever category string the model returns, with no sanitization, while the AI-rename path does sanitize. Same class of bug as the scheduling one, really — one code path got the careful treatment and a sibling path didn't. Fixing that now, before v1.3.2 becomes the version people actually pull down.
Try It
Prism Organizer is MIT-licensed and up on GitHub now: github.com/J-Akiru5/prism-organizer. npm install -g prism-organizer gets you a standalone binary with zero Python setup, or clone it and pip install -e . if you want to poke at the source. Dry-run is the default on every destructive command, nothing gets permanently deleted without an explicit backup folder, and prism-organizer undo reverses anything that goes sideways.
If you try it, or find bug #4, I'd genuinely like to hear about it — open an issue, or find me at jeffdev.studio / Syntaxure Labs.



Top comments (0)