How we build VolunteerPress with AI, the checks every change has to pass, and the bug those checks missed.
One evening in July, before version 1.0 shipped, every automated check on VolunteerPress was passing. At the time that was 541 PHP tests, all of them green. Then I did the last thing I do before I accept any change, which is open the site in a browser and use it the way a volunteer would. The calendar showed a shift at one time. The shift list on the same site showed the same shift four hours off.
Nothing had failed because no test compared those two screens. Tests only confirm what someone thought to ask, and nobody, me included, had asked that. I wrote the bug up, and the next work session began with a search instead of a fix: find every place the plugin shows a date or a time, and check each one. I knew of two. The search found five. Two of the five were emails, the automatic ones a volunteer receives and the ones a coordinator sends to a group. Had that shipped, a volunteer could have been told to show up at the wrong time by the tool that exists to get them there. It was fixed before launch, and all five places now run through one shared piece of code.
In the first post of this series I wrote that "AI-assisted development" sounds hands-off and isn't, and I promised a full post on how it really works. This is that post, and the story above is the short version: the AI writes the code, and it's still a person's job to find out whether the code is right. I built version one alone, so the stories here are mine. The rules that came out of them are the ones our whole team works under now.
For my own work I use two AI tools, and they have different jobs. Claude Code works inside the codebase, where it reads the files, writes the change, and runs the tests. The second is an ordinary Claude chat that can't touch the code at all. It helps me plan a change and writes the instructions Claude Code will follow. Later it reviews Claude Code's report on what it did. I sit between them. What gets built and how it should behave are my decisions. The instructions go one way and the report comes back the other through me, and nothing moves forward until I've read it.
That puts AI on both sides of the work: one tool writes and another reviews. It doesn't put AI in charge. The second chat can flag a problem, but it can't approve a change, and neither tool decides what VolunteerPress does or how it's built. Those decisions, the final read, and the commit are mine.
Every change starts as a filed issue that says what "done" looks like. For mine, Claude Code gets a fresh session each time, so it works from the project's written rules and the issue in front of it. When it finishes, it writes its report, and four checks run: the PHP coding standards check, the JavaScript lint, the JavaScript unit tests, and the PHP test suite. Right now that's 620 PHP tests and 21 JavaScript tests. The second Claude chat and I go through the report, I test the change in a browser, and only then does it go in.
Claude Code is not allowed to take that last step. A commit is the moment a change becomes part of the project's permanent history, and Claude Code can't stage files, commit them, or push anything to GitHub. I do that by hand, after the review and the browser check. Committing is where someone says "this is good, and I'm responsible for it," and that someone should be a person whose name is on the project.
Reviewing a report means reading for what didn't happen. In June, one session came back reporting success, with all of its unit tests passing. The change was almost entirely database queries. A line near the bottom of the report mentioned that the integration tests, the ones that run against a real database, hadn't run in that session, even though they had run in every session before it. The second chat flagged it in its review. Its verdict was that the code was probably fine, and that "probably" isn't good enough for the part of the plugin that reads and writes a nonprofit's volunteer records. I agreed, so the work didn't count as finished until those tests ran. They passed with no changes needed, and the standing instructions now name the exact test script so a session can't skip it.
The project keeps a plain-text file of standing instructions that an AI tool reads at the start of every session, and it grows each time a session teaches us something. I also ask for evidence. When I had Claude Code compare the website's documentation against what the plugin really does, every problem it flagged had to come with the file and the lines it was based on, and anything it couldn't back up had to be labeled as opinion. It also had to list what it was unsure about separately, because being unsure is a finding too.
If you run a nonprofit's website, here's what this means for you. VolunteerPress keeps your volunteers' names and contact details in your own WordPress site, and the code that handles them passed all four automated checks before it was released. That lowers the odds of a bug. It doesn't bring them to zero. Version 1.0.1 followed a day after 1.0 with fixes from the first round of feedback. What I can tell you is that the source code ships inside the download for anyone to inspect, and that when you report a problem, we write it up as an issue and it goes through the same process.
The same rules apply to everyone who contributes. Contributors can use whatever AI tool they like, or none. I've named Claude throughout because it's what I used to build version one, but VolunteerPress isn't tied to any AI company or tool. Every change, whoever makes it, has to start as an issue, pass the four checks, and be reviewed before it merges. If you want to help build it, the way in is at volunteerpress.org/contribute.
If you're building something with AI and you've found a check that catches what the others miss, we'd like to hear about it. And if you run a nonprofit's site on WordPress, download VolunteerPress from volunteerpress.org and tell us what happens: hello@volunteerpress.org.
Top comments (1)
That calendar and shift list bug is the classic integration blind spot with coding agents. When Claude Code adds a feature or writes tests, it tests the function output against the internal model. It rarely thinks to write an end-to-end check verifying that two different UI templates and an outbound email template render the same timestamp with the same offset. Separating the planning chat from the repo executor helps, but the structural fix is forcing the plan to list every user-visible surface where that state appears before the coding agent touches a file.