Skip to main content
Back to all posts
Oct. 20269 min read

Actual Budget's CI shipped a bug that saved 10.23 as 1,023. I gave GitLab Duo the release instead.

Still Works moves Actual Budget onto GitLab with a release pipeline that only ships a release if it still does everything production already does. GitLab Duo writes browser checks, keeps the ones that pass on production, and a held release goes to Duo Developer and Duo Code Review until the guard lets it ship. Replayed on eleven regressions the maintainers labelled: 2 to 3 stopped, against 1 for a smoke test.

GitLab TranscendGitLab DuoDuo Agent PlatformCI/CDPlaywrightGoogle Cloud RunOpenTofuActual BudgetHackathon
I wrote this post for the GitLab Transcend Hackathon, Life After Code (October 2026, Path B, Hands-off), and built Still Works for the same submission. The source, every pipeline, merge request and issue mentioned below are public on GitLab at gitlab.com/gitlab-ai-hackathon/transcend-october-2026/3846496/showcase. The Cloud Run deployment comes down after judging, so there is no live button here.
The claim in one sentence: replayed on eleven regressions Actual Budget's maintainers labelled, each of which passed their CI and shipped, the guard stopped 3 (writing checks without running them) or 2 (calibrating on production first, the version running live) against 1 for a smoke test with no Duo, graded against the maintainers' own published images for the release before, the broken release and the fix. That still means most regressions get through, and the guard also holds some releases that changed behaviour on purpose; both are counted below.
The Still Works README on GitLab: the heading Still Works, the line "A release only ships if it still does everything production already does", and the story of the 25.7.0 amount bug being held on staging.
Still Works: Actual Budget moved onto GitLab, where a release only ships if it still does everything production already does.

CI tests what the author thought to test

In July 2025 Actual Budget shipped version 25.7.0. Type 10.23 into the amount of a new schedule and it saved 1,023. The change had passed every test in the project's CI, and it sat in people's budgets for two days before 25.7.1 fixed it. That kind of bug is not rare. When I pulled the list on October 19, Actual's maintainers had labelled 122 issues as regressions, and each one got past CI too, because CI tests what the author of a change thought to test. Nothing checks that a release still does what production already does.

GitHub issue #5266 on actualbudget/actual, "Cannot insert a decimal in any of the amount fields in dialog boxes", labelled regression and closed, where the reporter says typing 10.23 inserts 1,023.
Actual Budget 25.7, July 2025: typing 10.23 into an amount field gave 1,023. The maintainers labelled it a regression.

No model decides whether to ship. A browser does.

A merge to main is tested, scanned and packaged, OpenTofu configures Cloud Run, and the release goes to staging. Then GitLab Duo's Release Guard flow reads the commits between production and the release, names what users rely on that this change could break, and writes Playwright checks for it. It runs every check against production first and keeps only the ones that pass there: a check that fails on production is wrong about the app, not evidence about the release. Duo never sees the new release running.

Then the pipeline decides, without asking the model anything. The kept checks run twice more on production, then on staging. If one that held on production fails on staging, the release stays on staging and the Regression Report flow opens one issue: the commit it blames, the check that failed, what staging showed, and what a fix has to keep. When everything holds, the release goes to production with a GitLab Release, Duo's release notes and a note on every issue it closes, and the checks that vouched for it run against production every six hours.

GitLab pipeline #2984538340, passed, with three stages: deploy (deploy-staging, production-baseline), guard (calibrate-on-production, guard-staging, write-release-checks) and release (create-release, promote-production, record-production-checks, write-release-notes, and a manual promote-held-release).
Each merge deploys to Cloud Run staging. Duo writes browser checks, they are calibrated on production, the guard runs them, then promotion.

The refusal

I put the change behind 25.7.0 back in as a merge request. Duo Code Review, using this fork's amounts-and-money review instruction, flagged it ("typing 12. returns 12, so the . is dropped") and I pushed it past, the way reviews get pushed past. Duo read the release and wrote a check that types 12.50 into a new schedule. Production showed 12.50. Staging showed 1,250. The release stayed on staging, Duo opened an issue naming the commit, and production was never touched.

GitLab issue #7 created by Regression Report, "Typing 12.50 as a new schedule amount is listed as 1,250 instead of 12.50", labelled regression and release-blocked, with sections What the check expected, What staging showed and Suspected commit 717eec8d.
The refusal: staging showed 12.50 as 1,250. The release never left staging, and Duo Regression Report opened this issue.

Duo checks Duo

Then I wanted to know whether Duo could also fix what it caught. I put the change back in again and gave each issue the guard opened to Duo Developer. It took seven merge requests. Duo Code Review approved all seven before they merged, and the guard still held five of them. Nine of the failures it caught after that approval were wrong amounts: 5.5+2.5 saved as 30.50, 1250.50 saved as 125.050,00, 1,00 typed over a selected amount applied as 100.00. None of them reached production. Production now shows thousands separators while you type, a feature Actual's maintainers reverted twice for this same kind of bug.

A table in the README listing fixes !2 to !8: for each, the issue it answered, what Duo Code Review said, the guard's result on staging (held 3 of 6, held 3 of 5, held 1 of 5, held 2 of 4, promoted, held 1 of 5, promoted) and what the guard caught.
Duo Code Review approved seven Duo-written fixes. The guard still held five: nine wrong amounts that never reached production.

Issue to production, with nobody in between

In that loop I still merged each fix and assigned each issue myself. So I moved both into the pipeline, and gave it a bug real users reported upstream that is still open in 26.10.0: skip a bill's next date, rename the schedule, and the bill comes back as due (#9167). I reproduced it on production, filed it, and assigned it to Duo Developer at 19:58. Nobody touched it after that. Duo Developer opened a fix nine minutes later. Duo Code Review sent it back once, because the new test could pass without the fix, and the pipeline handed that back to Duo Developer. The second version was approved at 20:26 and the pipeline merged it. Duo wrote five checks for editing schedules, all five held on production and on staging, and the fix was in production at 21:40.

Merge request !10, "Keep a skipped schedule date when editing unrelated fields", merged: GitLab Duo requested changes, the pipeline's drive-fix job mentioned Duo Developer, and Duo Developer replied that it moved a MockDate call so the test no longer depends on test order, and listed the tests it ran.
Hands-off: Duo Code Review requested changes, the pipeline handed them to Duo Developer, and Duo Developer pushed the fix.
The guard-staging job log: five schedule checks, each marked pass, followed by "verdict for the staging URL: promote (0 blocking of 5)" and Job succeeded.
Duo wrote five checks for schedule edits. All five held on production and staging, and the guard's verdict was promote.
The Schedules page of Actual Budget's demo budget on production, listing Internet bill, Phone bills, Utilities, Wedding and Broadband with their next dates, statuses and amounts.
Production after the fix: skip a bill, rename it, and it stays skipped. Upstream issue #9167 is still open in 26.10.0.

How I know it works

I did not write the bugs. I took eleven regressions Actual's maintainers labelled, each of which passed CI, shipped, and was fixed in a later release, with published images for every version. Each one was replayed from a branch holding only the release before and the release that broke it, so the fix never existed while Duo wrote its checks. A regression counts as stopped only if a check passed before, failed on the broken release, and passed on the fix. A smoke test with no Duo stopped 1 of 11. The guard that writes checks without running them stopped 3. The guard that tries its checks on production first, the one running live, stopped 2. Every catch was run five more times on each of its three releases, and a script fails the build if the README and the data ever disagree.

What is not claimed

The replay is graded by the maintainers' own labels and published images, not by me, but it is eleven regressions, and the guard lets most of them through. The live version stops 2 of 11, one fewer than the version that does not calibrate, because calibrating keeps more checks and holds more intentional changes (14 non-catch blocks against 6). It pins what production does today, including production's own bugs. It misses regressions in places the diff does not obviously touch, such as an empty-state page. It only holds what its checks cover: one release shipped a wrong amount (deleting two digits of 1,250 applied 1.20) because no check typed it, and a later Duo fix shipped the correction. It cannot show that a fix works, only that it broke nothing production already does; for #9167, Duo's unit test, Code Review and my own check on production after release covered the fix. Releases that change behaviour on purpose wait for a person to press promote-held-release, and one guard run takes about half an hour of Duo time.

Related project

Still Works: Actual Budget moved onto GitLab, where GitLab Duo writes browser checks from what production already does and a release only ships if it still does all of it

View the project