An AI Credit Balance That Hit Zero With No Warning
A model balance that hit zero with no warning, a browser that swapped my icon for a letter, and two fixes for things that will never tell you they broke.
Sunday morning I opened Duskglow on my phone to start the entry I had meant to write the night before, and Luna did not answer. The screen said so in plain words, "Luna didn't answer," and the try-again button did nothing. By the evening I had traced it, fixed it with my credit card, and shipped a one-line change for an unrelated picture. The two problems had nothing in common except the thing that matters: in both cases something I depend on failed quietly and nothing told me.
The error that was kind to the user and useless to me
Duskglow gives a person a generic failure on purpose. Someone writing at eleven at night just needs to know the message did not go through, and the status code can stay in the log. So the clue was never going to be on the phone. It was in the server logs, and the logs were clear: every call to the language model that morning had come back 402, Payment Required. Twelve of them. The last successful call was Thursday night. Nobody had talked to Luna between Thursday and Sunday, so for up to two days the failure could have been sitting there, and nothing would have said so. The someone who found it was me.
I did not know what 402 meant for this vendor, so I read their billing page rather than guessing. Google had moved my API account from pay-after to prepaid credits. The page says that on prepaid the balance is drawn down in near real time, and that at zero every key on the account stops at once. The project does not fall back to the free tier on its own. And the thing that stopped me: nowhere on that page is there a low-balance alert. Nothing warns you. Auto-reload is offered as the remedy, and I had set nothing, because I had not known there was anything to set.
What I had instead was a line in my own reference notes about a monthly spending cap. That line was true under the old billing model. I wrote it once, months ago, and never re-read it, which is the exact pattern I named in a post earlier this month about settings I set once and never checked. Here it was again, in my own notes this time rather than a vendor dashboard. I am apparently going to keep learning this one.
Five dollars and a date
Fixing it was quick. I bought credits and turned on auto-reload, five dollars added whenever the balance drops under five. The next call worked.
Sizing the reload is worth a paragraph because it is a product decision, not a billing one. With the beta about to start, the spend that comes from real people talking to Luna is small. The spend that is large is my own adversarial testing, where a suite of hostile prompts runs against the live model in one sitting. A five-dollar floor covers users comfortably and it does not cover a long test run. I prefer it that way. The automatic rule is sized for the people using the app, and the expensive activity, my own testing, gets a deliberate top-up by hand before it starts.
Then the part that outlasts the fix. This account is now in my settings inventory, the list of every setting outside my own code that can stop the product, with a reading schedule beside it. The balance gets read before the first beta invite goes out, and again at the start of every working session while the beta runs. If this happens in the middle of the beta it is an outage for every tester at once, on a frozen build, with no alert from anyone. The only alarm is the one I set. That is the whole lesson of the morning, and it has nothing to do with this particular vendor.
A grey letter where the eclipse should be
The second problem was smaller and took longer to understand. Saturday night's reminder arrived, but the icon beside it was a grey circle with a "D" in it instead of the app's eclipse mark. A letter. Where did the letter come from?
My first instinct was that the notification code was asking for the wrong picture. It was not. The files were fine and the server returned them at the right addresses. So I went and read the browser's own source, because the browser drew that letter and the browser's code says when. Chromium generates a letter icon from the site's name whenever the icon it was handed is empty. And its resource loader hands over an empty icon when the fetch for the picture fails or runs past a ninety-second timeout. In other words the browser asked for the eclipse and never got the bytes.
Why not? I have no way to watch my phone's radio at 9:30 pm. What I can see is how the picture was served, and it was served with a header that said do not cache this, check with the server every time. So every single notification, on a phone that had been idle for an hour on whatever signal it had, had to fetch the icon over the network at the moment of delivery. Most nights that works. Saturday it did not.
The fix was one rule in the hosting config. Keep the picture for a week, and for a month after that serve the old copy while quietly fetching a new one. The eclipse should now come from the phone's own cache. I am stating the mechanism with medium confidence, because the browser's behaviour is from its source and the network at that moment is not something I can observe. If the letter shows up again with the cache in place, the fetch was not the cause and I go look at the delivery path next. I wrote that down so the next me does not re-derive it.
One push, sent
Tonight was the test I set up in a post a few days ago: a draft I started late in the morning and left unfinished, and a reminder that should say so instead of sending the generic nudge. I did not touch the draft after 8:30 pm. At 9:30 the server found one reminder due and sent one. The draft is still unsaved and the entry does not exist, which is exactly the state the rule needs. Whether the notification carried the draft sentence and the eclipse beside it is on my phone's screen, not in any log I can read, so that is tomorrow's first look.
Mental Models
The vendor will not send the email. If a dependency can stop your product, assume no one will tell you when it does. Put the reading on your own calendar with a date. The only alarm is the one you set.
A kind error moves the clue. A generic failure on screen is right for the person using the app. It means the specific cause lives in the logs, so that is where the search starts.
Read the document that drew the symptom. Twice today the answer was in something someone else wrote, a billing page and a browser's source code. Guessing from the symptom would have had me rewriting notification code that was already correct.
Remove the step you cannot watch. When a step runs on a device you cannot observe, do not hope the network is there. Cache the picture so the step never happens at the moment that matters.