Goread2 - Chapter 5: Getting Serious About Process
This is the fifth in a series of posts wherein I attempt to recount the history of Goread2 as it approaches a state in which I might actually try to share it more broadly.
At some point in the development of a software project, work shifts from “building things” to “knowing things”1. Knowing whether the code works. Knowing whether the system is healthy. Knowing what still needs to be done.
Testing Gap
The multi-user transformation back in August had shipped with a commit message claiming “90%+ coverage.” By late November, with a proper coverage tool run against the handlers package, the actual number came in at 9.7%.
This is not an unusual situation. Coverage numbers in commit messages are often aspirational, or measured against a subset of the code, or simply optimistic. What was unusual was the speed at which the gap was closed once it became a priority.
November 28 and 29 were a testing blitz. A single commit on the 29th, Add comprehensive handler tests to increase coverage from 9.7% to 33.6%, ticked through the list of handlers that had never been tested: DeleteFeed went from 0% to 100%, MarkRead from 0% to 100%, ToggleStar from 0% to 100%, eight other handlers from zero to something meaningful. The commit message annotated each one individually, which has the quality of a student showing their work because it demonstrates something was actually thought about rather than just thrown at the wall.
By the end of the weekend, handler coverage was 33.6%, database tests added, edge case tests for the services package increased coverage from 49.3% to 57.8%, integration tests added for the critical multi-user isolation workflows. Frontend test files that had quietly broken at some point were found and fixed.
None of this testing revealed new bugs, which is its own kind of result. It meant the November hardening sprint actually fixed what it said it fixed, and the code’s behavior matched the intent. One could argue that is what a test suite is for: creating the conditions under which future bugs announce themselves immediately rather than silently.
Fun With Monitoring
Alongside the testing push, we needed proper visibility into what was happening in production. The plan was straightforward: set up Cloud Monitoring dashboards to track Datastore operations, App Engine instance hours, bandwidth, and request rates. Add alert policies so that cost spikes would trigger a notification rather than a surprise at the end of the month.
On November 19, six alert policies and a ten-widget dashboard were committed. This was immediately followed, over the next three days, by a sequence of commits that tells a familiar story:
Fix: Add working App Engine dashboard, document Datastore metrics limitation.Fix: Remove unavailable App Engine HTTP metrics from Cost Tracking dashboard.Fix: Remove broken billing dashboard and clarify cost tracking options.Fix: Create minimal dashboard with only working App Engine Standard metrics.
It turns out that App Engine Standard, which the app had migrated to, does not expose many of the metrics that App Engine Flexible exposes — including request_count, which is a fairly foundational thing to want to graph. The operational dashboard we had planned ended up being replaced with a billing-focused one: daily cost trend, cost breakdown by service, instance count, month-to-date spend. Less “how is the app performing” and more “how much is the app costing,” which, given the events of Chapter 3, was arguably the more important dashboard to have anyway.
The six alert policies — Datastore reads over 1,000/minute, writes over 500/minute, instance count over 5, egress over 100MB/minute, a 2x read spike detector, and a 5xx rate over 1% — survives intact and were deployed successfully. Whether App Engine Standard would actually fire them in the ways intended remained, per the documentation, “LIMITED.”
Issue Tracker
The third thread of this chapter is the one that is the most interesting to think about from a project-management perspective, and the most unusual: we adopted Beads as the project’s issue tracker.
Beads describes itself as “AI-native issue tracking.” What this means in practice is that issues are stored as JSONL files directly in the repository. No GitHub Issues, no Jira, no Linear. The issue tracker lives in the git history alongside the code it tracks. Each issue is a JSON object with an ID, title, description, status, priority, and whatever other metadata is relevant. The whole database travels with the repository.
The reasons for this design become clear when you look at how issues are actually used in this project. The November code review that opened nineteen issues at once (from Chapter 4) was done by reading code and writing structured issue descriptions with file locations, code snippets, and pseudocode for the fix already included. Those were not written for a human to eventually read and act on. Rather, they were written as machine-readable task specifications. When a developer agent (human or AI) picks up an issue, it has everything it needs: the location of the problem, the nature of the fix, sometimes example code. The issue IS the spec.
This became explicit in January, when AGENTS.md was updated to document a full multi-agent orchestration pattern. A supervisor agent coordinates work by creating Beads issues as task specifications and never writing code directly. A developer agent receives a task via bd show ISSUE-ID, implements it, and pushes. A reviewer agent checks the implementation and either approves or requests changes. The whole workflow is mediated through the issue tracker, which is just files in the repo, accessible to any process that can read the filesystem.
Whether or not you are running multiple AI agents, having the issue tracker in the repository has practical advantages: it is versionable, diffable, and never goes down. The prefix on issue IDs even changed during this period (from goread2- to the shorter gr-) and the migration was just a find-and-replace in JSONL files, committed to git, visible in the history like any other change.
The three threads of this chapter are expressions of the same impulse: wanting to know, with confidence, what the state of the system is:
- Tests tell you whether the code does what it is supposed to do.
- Monitoring tells you whether the system is behaving the way you expect in production.
- An issue tracker tells you what is known to be wrong and what has been decided to fix it.
Together they create a feedback loop that turns “I think it works” into “I can verify that it works.”
That loop takes time to build. It doesn’t produce features users can see. But without it, the code review in Chapter 4 would have been the end of a conversation rather than the beginning of one.
Next: are we done yet?
-
At least until the product managers show up. ↩