On Opening the BooksA read-only health check · Three rounds of fixes and deployment
A top-up had been refunded to the card, and the corresponding amount had been deducted from the wallet, but the ledger still showed it. I came across this entry during a financial health check of GGBOYS. That afternoon I asked, “How did this entry go missing?” On investigation, the money was not missing. The record was.
When a platform can take payments and send refunds, it looks, day to day, as though it is working normally. Only when the wallet and the ledger are laid out side by side does it become clear that they tell different stories. If we waited for a transaction to fail before investigating, this successfully refunded payment would probably never have come up.
At the time of the check, GGBOYS had been live and handling real money for less than a month. It was a marketplace for in-game items. After a buyer pays, the money stays in escrow; because the trade can be reversed, the seller’s funds cannot be released until the protection period ends. On October 20, 2026, I asked Claude Code to check the whole system, wanting to know whether these constraints were actually being enforced in production.
Leave the books alone for now
Before the check began, we had to establish which version we were examining. The lead agent compared the code actually running in production with the version in the repository, then made read-only queries against production data to see whether the report’s findings had a basis in reality. A copy of the code on the desk is not necessarily the copy running in the shop. The distinction sounds minor. We ran into it again later, while preparing the second deployment.
The check covered 9 areas, including the release of funds for sales and rentals, as well as withdrawals, top-ups, permissions, and the ledger. Work was divided among 18 subtasks using Opus models. In each area, some looked for problems while others challenged and rechecked the findings. The check itself took 31 minutes and changed no code, data, or configuration. The fixes and deployments that followed stretched over several days.
One overlap during that half hour was worth recording: 6 subtasks independently found the same problem with dispute rulings. Money awarded to a seller could be withdrawn before the transaction risk had passed. An earlier review had examined this too, but had focused on the provisions allowing funds to be withheld. It had not gone on to ask what would happen if the trade were reversed after the money had been paid out.
Having more people look can certainly be reassuring, but finding the same issue repeatedly does not establish that it is real. The lead agent continued checking disputed findings, revising severity ratings that were too low as well as ones that were too high. For me, the useful thing about those 6 findings was that they brought attention back to the same business consequence. Between having a rule written down and the way money actually moves, there is still the implementation.
The report needs checking too
Once the report was ready, the issues fell into two groups. One concerned clear rules that the implementation had missed, or straightforward technical errors; these could be fixed first. The other would change when money was released or who bore a loss, and those decisions were mine. Before sorting them, the agent also checked the existing rules so that questions already answered would not be handed back to me. I did not need to sign off on every coding error, but I could not let whoever wrote the code make compensation promises on the platform’s behalf along the way.
Work on the first batch began on October 20, and it went live that evening. The missing entry from the opening was part of it. The refund had been issued through the payment provider’s dashboard. When the notification arrived, the wallet was debited correctly, but no corresponding entry was made in the ledger. Refunds initiated through the platform’s own admin interface already handled both together. The missed path was changed to do the same. The ledger correction was rehearsed before being applied, then each entry was checked. The wallet and ledger agreed again.
In an imagined old countinghouse, it would be as though the money had been paid and the entry crossed out in the book on the counter, while another book still showed the original amount. The bookkeeper would probably not close that second book simply because payment had been made. These days, updating several records separately seems to save the work of copying entries by hand, yet someone still has to turn up the one that was missed.
The same batch also included a correction to the report itself. The report said that a group of internal operations with overly broad permissions had no callers in the admin pages, so their access could all be restricted together. Before making changes, the repair session checked the callers again and found that some admin buttons did still use them. Following the report directly would have broken those buttons. The operations were therefore handled according to their actual use: those intended only for internal use had their callers restricted, while those needed by the admin interface received the appropriate permission checks.
A mistaken sentence in a report is hardly unusual. What mattered to me was that the repair session did not treat that sentence as the end of verification. Even with a report claiming that the checks had been done, it still had to go back to the buttons and their calls. The first batch also filled gaps in existing rules, including restrictions on releasing funds after a chargeback. But “existing rules” only answered what should happen. Where exactly to make the change still needed checking.
The questions that were mine to answer
The matters that needed policy decisions were presented as multiple-choice questions, each with a recommendation and its reasoning. Only after I answered did work on the second batch begin. For dispute rulings, the decision was to refund the buyer’s share first, while keeping the seller’s share in escrow until the conditions for release were met. A ruling settles how the money is divided between the two parties. It cannot also make an ongoing transaction risk disappear.
Another question concerned compensation. If a seller owed compensation but could not pay, should the platform advance the money? That went beyond whether the program’s calculations were correct. I decided that buyers would be compensated from money actually recovered from the seller, with the outstanding debt still pursued and no advance from the platform. Further details about subsequent payments arose during implementation. The session stopped to ask again, and continued writing only once the answer was clear.
For the second batch’s changes to escrow and the ledger, we had an Opus review and also asked Codex, using GPT-6 Astra, for a read-only second opinion. The reviewers looked for evidence on the assumption that something here might be wrong. The rules for dividing money might read sensibly while their execution could still get stuck. Bringing another judgment to bear on such points was more reassuring than a long passage telling me everything was “complete.”
The code was finished around the early hours of October 21, and had passed review and testing, but it did not go live immediately. By the time deployment was being prepared, other work had changed the main branch, and the database changes conflicted too. A separate session handled the merge again, reviewed the merged sections, and reran the full test suite. The changes reached production only on October 22.
The third batch: put the issues back on the table
Before work on the third batch began, the candidates were checked again against the code as it stood then, and I chose which ones to include. The rules for refunds to the original payment method were also revised here. At first, the refundable amount had been calculated across the whole wallet. It was later calculated per card, but another check still found gaps in what the calculation covered. My requirement was simple: refund the money to the card used to top up. Under the final rule, each card could receive a refund of its own deposits that had not yet been counted as spent under the accounting rules.
“Original payment method” sounds clear enough on its own. Only when a wallet holds balances from different sources does the need to specify what belongs where become apparent. I will not go into the accounting method here. The revised refund rules went live with the third batch on October 24. Just because the previous version had already been written into code did not mean it fully expressed what I had intended.
This batch also addressed cases where an order was cancelled at the same time as it was shipped. When a refund was about to be executed, the order’s status had to be checked again. If the conditions were not met, no money should move; an earlier read was not enough to decide. The checks also extended to monitoring coverage: every transaction that ought to be checked had to be included, rather than quietly missed because it fell outside the processing range.
The effect of a manual clearance was narrowed too. A single verification could no longer become a lasting exemption. Where mismatched accounts prevented funds from being released, holding the money was right, but someone also had to receive an alert. Leaving a line in a backend log was not enough. Money not being released in error did not mean it had been properly attended to. The third batch went to production in two parts, both completed on October 24.
Before deployment, the old alerts were still there
To return to something that happened during the second deployment: that batch tightened the checks before releasing funds. If a transaction’s status could not be confirmed, release would be deferred. But the deployment checklist noted that production data still contained historical alerts that had never been closed. The new rules would take them as evidence of a current problem. If we simply pushed the code, funds normally due for release would be held back too.
We could not casually erase the alerts just to get the new code working. First we had to confirm that the trades in question had since been rechecked and had not been reversed. Only then could the old records be dealt with. Before execution, the data correction was rehearsed in both the test and production environments without persisting any changes. We confirmed that the rehearsals had left no changes behind and prepared the reverse operation. Only then did we apply the correction and deploy.
This felt more like clearing a bookkeeper’s desk than changing a condition in code. The new rule was written, but old slips of paper still lay underneath. The slips recorded what had happened in the past; the program might read them as reasons to keep holding funds now. The same record took on a different meaning inside the new logic. A check the next day showed that the first payment due after deployment had been released on time, without being mistakenly blocked by the tighter checks.
Each of the three batches was implemented in an isolated workspace. New tests were first run against the code from before the changes, to see whether they really caught the original errors. After merging, the full suite was run again. Across the two parts, more than 10,000 tests passed before deployment. I still confirmed each production deployment separately. Each step had a rollback point, and rollback instructions were included in the handoff records. Database changes only added things, never removed them; rollback reverted only the code, with separate reverse operations for any data changes. Production logs still needed watching afterward before the work could be considered done.
Another set of accounts for those days
The check itself took only those 31 minutes. By the time both parts of the third batch were live, it was the night of October 24. The related usage records extended into October 25: more than 4,000 model calls, the vast majority to Opus 5.5, with a small amount of work done by Sonnet 5.5. Codex’s GPT-6 Astra took part in reviewing the second batch. At API list prices, the total was roughly 400 to 500 US dollars.
Including cache reads, the logs added up to over a billion tokens. Most of these were repeated reads of existing context; they could not be treated as over a billion characters newly written by the models. Recorded output was on the order of a million tokens, and some early subtasks’ output had not been fully logged. The sessions also contained deployment work that could not be separated out. This conversion was therefore only a rough account, not the precise cost of the financial health check.
How much work the models saved me is something this rough account still cannot answer. Looking back at the refund at the beginning, the wallet and ledger now agree. That entry can be crossed off for now.
Writing record: Codex wrote this article on 2026-11-03, from 16:09 to 16:16 (US Eastern). The main thread used GPT-6 Astra (gpt-6-astra), with reasoning effort set to ultra. It dispatched 2 additional threads itself, one to check that the article stayed within the boundaries of the redacted material and one to review the prose style. Both also used GPT-6 Astra and ultra. Across these 3 threads, total input was 1,611,249 tokens (including 1,498,240 read from cache), and output was 17,303 tokens (including 4,374 reasoning tokens). Claude Code (Claude Opus 5.5) had prepared the source material beforehand; that work is not included in these figures.
Translation log: translated from the Chinese original by Codex on 2026-11-03, 17:37–17:40 (US Eastern). Main thread GPT-6 Astra (gpt-6-astra), reasoning effort ultra; it spawned 1 sub-thread: 1 on GPT-6 Astra (effort high). Together they read 428,415 tokens (388,864 of them from cache) and wrote 7,204 (448 of them reasoning).
“That entry can be crossed off for now.”
BASTION NOTE · 022