This blog post was written with assistance from OpenAI Codex, and edited manually.
I was testing a budgeting app when I noticed something weird: Left showed -$100, even after I had cleared my test income and expenses.
As a software engineer, my first thought roughly was, โWhere did the balance calculation go wrong?โ
As a QA engineer, my next thought roughly was, โWhat sequence of clicks got us here?โ
That second question cracked the case, with help from Codex.
Assigning money isnโt spending it
The app uses envelopes to organize money. You might assign $100 to Rent, then record a rent expense against that envelope.
Those are two different actions:
- Allocation: Move money from Left into an envelope.
- Expense: Record money spent from that envelope.
A reset clears an envelopeโs assigned amount. If you choose to return its unspent money, that money goes back to Left.
That works until you change the history after the reset.
How we hit the bug
Hereโs the sequence that mattered:
- Rent had $100 assigned.
- I recorded a $200 expense against Rent.
- I reset Rent and chose to return the unspent money.
- Because Rent was overspent, the reset returned $0 and cleared the $100 assignment.
- I deleted the $200 expense.
Deleting the expense removed it from the spending history. But it didnโt restore the $100 that the reset had cleared.
Later, after the other test transactions were reversed or deleted, that missing adjustment left the account at -$100.
The money didnโt disappear randomly. Two actions that worked individually didnโt work correctly together.
Why the code missed it
The expense deletion code refunds Left when an expense has no envelope.
That makes sense for a normal envelope expense: deleting it should reduce that envelopeโs spending, without moving money between envelopes and Left.
But this expense belonged to a cycle that had already been reset.
The reset had used the expense to decide how much money to return. Once I deleted that expense, the information behind that decision changedโbut the returned amount stayed the same.
That was the gap.
Finding the evidence
The dev log was useful for checking requests and errors, but it couldnโt fully explain the balance.
The database history gave us the actual story. It included allocations, resets, and deleted transactions, so we could follow the sequence instead of guessing from the current screen.
Thatโs a big QA lesson: the final numbers donโt always tell you how the app got there.
And itโs a software engineering lesson too: when one operation depends on another record, editing that record later can affect more than its own row.
The proposed fix
The fix is to account for how an expense edit or deletion changes a completed reset.
For this case, without the deleted $200 expense, Rent would have had $100 unspent when it was reset. The reset should therefore have returned $100.
Deleting that expense should add the missing $100 to Left, bringing the account back to $0.
We also need to handle expense edits. Reducing an old expense could increase the amount that should have been returned; increasing it could decrease that amount.
Before coding this, we need to confirm that the reset history contains enough information to calculate the correction reliably. If it doesnโt, we should explicitly store the resetโs original assigned amount and mode.
How Iโd test it
Iโd start with the exact sequence that exposed the bug, then cover related cases:
- Delete an expense after a reset that returned $0.
- Edit an expense after a reset that returned some money.
- Repeat the flow across multiple resets.
- Check resets that mark money as spent instead of returning it.
- Confirm that ordinary expense edits and deletions still behave correctly.
Iโd use unit tests for the calculations and a click-through test for the full user flow.
The fix is still a proposal. The account correction would also be a separate, recorded action after we validate it.
This bug was a reminder that testing each button once isnโt enough. Sometimes the interesting failure is what happens when you click a few perfectly reasonable buttons in an order the code didnโt expect.
Leave a Reply