The Bill Was Never the Model
My AI credits were draining fast. I assumed the fix was a cheaper model. It was two copies of the same job, a missing lock file, and a heartbeat that kept reporting work that had already died.
This week I asked Claude to figure out why my DeepSeek and Claude credits were disappearing. I had a theory ready: too many flows, too expensive a model, route more work to something cheaper. I was wrong about all of it, and the real cause was more embarrassing and more useful.
The workload is a channel analyzer I run across about 87 YouTube channels. Extraction runs on a small local Qwen model, so it costs nothing. But a few steps, like verifying products and classifying buy links, call DeepSeek directly. Not huge per call, but it adds up if the job runs long enough.
The first thing Claude found was two analyzer processes working on the same channel at the same moment. I had set up two worker loops on my Mac, one pointed at each local model server. The second script had a copy-paste bug: it read the first worker's handle file instead of its own. For four days, both loops had been processing the identical list in lockstep. Every verification call was made twice, and nothing was gained from the second worker, because it was never doing different work.
That was the easy find. The same check on my Proxmox container was worse. Its crontab fired the same full-channel script on two schedules, Monday morning and Wednesday, Friday and Saturday night. A full pass takes several days, because some channels have thousands of videos. There was no lock file, so every cron firing started another complete traversal on top of the ones still running. Three full passes were running at once, one of them from a manual launch I had forgotten about. That is three times the API calls, continuously, from a schedule that looked reasonable when I wrote each line of it.
The fix on the container was a flock guard, so a run that is still going turns the next firing into a no-op. Later in the week I found that fix had only ever been applied on the box itself. It was never pushed to the repo, so a redeploy from GitHub would have quietly brought the bug back.
The third problem was the one that nearly fooled us. I kept getting progress notifications every few hours saying both workers were still going, at 433 of 573 and 156 of 574. Claude checked the processes, the ports and the log timestamps. Neither worker had run for hours. The heartbeat sender was re-reporting its last known numbers as if they were current. It had been telling me the system was healthy while it was idle, and earlier, while it was doubled up.
A few things I'm taking from this:
- Cost problems are often multiplication problems. I was looking at the price per call when the number of copies was the lever. Cutting the model price in half would have saved less than deleting one process.
- Anything long-running needs an overlap guard from day one. Cron does not know your job takes four days.
- A status message is not evidence. The heartbeat checked nothing about the workers. It only repeated a stored value. A health signal that does not touch the thing it reports on is decoration.
- A fix that lives only on the machine it was made on is not a fix yet.
The broader point is about where to look first. When the bill looks wrong, the instinct is to optimize the unit price because that is the part with a pricing page. The first question should be how many times the work is happening, and whether anything was supposed to stop it.