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:

  1. Rent had $100 assigned.
  2. I recorded a $200 expense against Rent.
  3. I reset Rent and chose to return the unspent money.
  4. Because Rent was overspent, the reset returned $0 and cleared the $100 assignment.
  5. 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

Your email address will not be published. Required fields are marked *