Skip to main content

← Back to Journal

Build Studio learns to watch what nobody pushed

Most of what broke in the last three weeks broke while nobody was touching it.

A nightly workflow that has been green since May starts failing on a Tuesday. A dependency you pinned in June picks up an advisory in August. Neither of those is a thing you did; both of them are things you find out about days later, if at all, because nothing was watching. Build Studio could tell you a great deal about a run you had just started and nothing at all about the repository you last pushed to a week ago.

That gap is most of this entry.

A Monitor tab

Build Studio now has a Monitor tab on the home view, beside Projects, Demos and Model. It lists the cross-project conditions nobody triggered: scheduled workflows whose latest run failed, and open dependency advisories, grouped worst-first, because that is the order a morning triage actually reads in.

Nothing is stored. Every row is derived on each poll, so an alert stays visible exactly as long as its condition holds — patch the advisory or let the nightly job go green and the row removes itself. There is deliberately no dismiss button. A stored acknowledgement drifts out of date, and a monitor still showing a vulnerability you fixed last week is a monitor you learn to stop opening.

CI failures also raise a desktop notification now, from the Electron main process, so they arrive with the app in the background. Those fire on transitions only — red-to-green as well as green-to-red, so you learn that a fix landed without having to go check. An unchanged failure never re-announces itself, and the first poll after launch only establishes a baseline. Otherwise every startup would greet you with alarms about last week.

One correction came out of building it. The CI light used to take its colour from the most recent run of any workflow, which on a project whose most frequent runs are nightly cron jobs meant it was quietly reporting the cron. One project here had shown a red CI light for days over a failing staleness gate while its actual push CI was green the whole time. CI now considers only push and workflow_dispatch runs, and the scheduled job it used to be showing has moved to the Monitor tab, where it belongs.

What npm will not tell you about a fix

The dependency half of Monitor turned out to be the more interesting problem, because the naive version of it is confidently wrong.

Build Studio's first pass read the lockfile and asked: does every current parent of this package permit the patched version? If not, it filed the advisory under blocked upstream — nothing to do until someone else ships. That is a reasonable-sounding question and it produces the wrong answer whenever the fix arrives by updating an ancestor rather than a parent. miniflare pins undici to an exact version, so the lockfile reads as a dead end. But miniflare arrives via wrangler, and a wrangler already inside the range in your package.json ships a miniflare carrying the patch. One command fixes it.

Every single row filed as blocked upstream on this installation was that shape.

The verdict now comes from npm audit fix --dry-run — from npm's actual plan — and not from the lockfile, and not from npm's own fixAvailable flag either, which is not reliable enough to act on. Advisories now sort into four honest states:

run one command      — reachable without a breaking change; the row prints the command
breaking bump        — a fix exists, but only across a major version; the row names which
blocked upstream     — genuinely nothing to take yet
no PR — decide       — a patch exists, no PR appeared, and we could not tell which of the above

Measured here, every advisory previously demanding a decision turned out to be clearable: fifteen npm ones with a single npm audit fix, and one Python pin that was simply old. Thirteen had piled up on a single project where nine needed nothing but a version bump. Demanding judgement where the answer is one command is how a list teaches you to stop reading it.

Two rules fell out of it that I would keep anywhere. The analysis refuses to guess — an ecosystem it has no reader for, an unreadable manifest, an audit entry that does not name the specific advisory all fall back to the vaguer label rather than asserting something false. A wrong "run one command" costs you a command that does nothing; a wrong "blocked upstream" hides a real fix, which is worse. And a fix that downgrades the vulnerable package is not a fix — that one needed an explicit guard, because a plan that removes the vulnerability by moving backwards technically satisfies the audit.

Alongside that, Build Studio now runs git fetch on your projects. It never did, which meant the CI/CD tab's "behind: N" was only as fresh as the last time you fetched in a terminal — so two projects here were displaying behind: 0 against remotes that had genuinely moved on. Which is the normal state right after merging a PR on github.com, i.e. exactly when the number gets read.

The gates only a person can pass

The other Build Studio change this period came from watching a run fail in a way that had nothing to do with the code.

A QA spec required that "a second person reviews the fixture diff for sensitive data and records reviewer/date in the manifest." No agent can do that. The reviewer correctly flagged it as blocking in all seven code-review rounds, the developer correctly refused to invent a reviewer each time, and the run hit its round cap having found zero actual product defects. Seven rounds of a machine and a machine agreeing that a human was missing.

Starting a run now scans that item's spec set — the item file, its PRD, and the documents the PRD links — for requirements no agent can satisfy: a second person, a manual review, a recorded reviewer identity, a sign-off, an owner decision. If it finds any, the first click lists them with file, line and the sentence itself, and offers Start anyway or Cancel.

It is advisory and never blocks a start. The point is not to forbid human gates — they are legitimate, and an agent should not be making those calls on my behalf — it is to have them resolved up front rather than discovered eight rounds in. The scan runs server-side on the start endpoint, so it reports to any caller, including automated ones with no screen at all. That mattered: an unattended job had started an item carrying six gates, including an owner decision, then rewrote the spec to declare the gate automated and marked the item Reviewed — while the test underneath was unchanged.

I should also record that the first version of that scan shipped with a path-traversal bug. It built its file list from three sources that agents write — a request parameter, a backlog item's prd: frontmatter, and a regex over PRD body text — resolved them without containment, and would happily read /etc/hosts and return every matching line in the API response. Found and fixed the same day, but it was on main in between. Both layers are guarded now. The uncomfortable part is that the untrusted input was repo content, which is easy to stop thinking of as untrusted when you are the one running the agents that write it.

Fazon: getting the data back out

Fazon had the busiest three weeks by volume again, and nearly all of it went into one thing: making the app hand over everything it knows.

The shipped export was deliberately human-shaped — a food-log CSV and a readable PDF, scoped to food entries over a chosen period. That is the right artifact for a spreadsheet or a doctor and the wrong one for a machine: four of the persisted record types, no stable row IDs, no lifecycle, no provenance, and none of the targets or coach history at all. Everything the coach knew about you was stranded in an app that could not hand it over.

There is now a machine-readable full-data export against a written contract, and the surrounding work is most of what "your data is yours" actually costs:

  • The nutrient tier the CSV and PDF carry went to the full thirteen-nutrient label set, plus a class of lab-sourced micronutrients with an explicit missing-versus-zero contract.
  • Timezones are captured at write time and resolved at export, instead of being inferred at read time from wherever you happen to be standing later.
  • A float-precision policy so a number does not change shape between JSON, CSV and PDF.
  • A delta mode with a cursor ledger, so a repeat export can ask for what changed rather than everything again.
  • The catalogue picked up a versioning contract and a pinned, verified source mirror with a staleness alarm, so the food data underneath the export cannot silently move.

The precision work is the part worth writing down, because it took seven review rounds and every round killed a test that was passing for the wrong reason. A gate that could not fail. Provenance the artifact reported about itself. An oracle built from the same source as the code it was checking, which is a very convincing way to prove nothing at all. None of those rounds found a bug in the exporter. All seven found a reason to disbelieve the evidence that the exporter was correct.

That is the lesson I am taking out of this period, and it generalises past Fazon: a green test is not evidence. It is evidence only once you can say what would have made it red, and check that the thing that would make it red is not itself derived from what you are testing.

DeskRhythm cuts 1.0, and makes iOS CI affordable

DeskRhythm moved from launch work to launch mechanics. MARKETING_VERSION is cut to 1.0 on the shipping targets, the TestFlight "What to Test" notes are authored through Fastlane and now fail closed if they are missing, and all thirteen open Dependabot advisories are cleared.

The more interesting piece is that iOS CI now exists at a price I am willing to pay. Running a full Xcode build and simulator suite on a hosted macOS runner for every push to a monorepo where most commits touch src/ or content/ is pure waste — macOS minutes bill at roughly ten times Linux. The suite is now tiered and gated on a paths: filter, so a web-only push runs the ubuntu job and nothing else, with cancel-in-progress so successive pushes coalesce.

Measured, end to end: 6 minutes 42 seconds from push to a green run, and a projected six to eight dollars a month before the plan allowance. That is worth knowing precisely, because the failure mode of expensive CI is not a bill — it is that you turn it off.

One guard came with it. Everything that can change iOS behaviour has to live inside the paths filter, which is true today by construction. If an iOS-relevant file ever moves outside ios/, CI goes silently green-by-absence, which is the worst possible way for a test suite to lie. There is a check for that now.

Android is on the backlog as a Phase 1.5 item rather than a promise.

Elsewhere

My Next. The repository is renamed from my-next-studio to my-next, which is what it has actually been since the marketing site moved here. Version 1.2.1 is prepared across all three apps with corrected privacy copy, Note gained its own iOS release gate, and the release gates now fire correctly instead of blocking their own tag. One real bug fixed: analytics was dropping events when a send failed rather than retrying them, so some of what I thought was quiet usage was quiet plumbing.

This site. Build Studio has a proper product sub-page now instead of a card pointing straight out to GitHub, the My Next family pages picked up consistent naming, and the footer carries X and YouTube links alongside GitHub. The Lighthouse budget that had been flapping is stable again, mostly by lazy-loading posters that were being fetched above the fold for no reason.

Välkomna. An SEO pass, the guest photo album finally mentioned in the hero copy where someone might actually see it, and a dependency sweep.

Sickla tunneln, Skrivhjälp and Finance Studio were quiet. Life Graph took dependency updates and nothing else.

What's next

DeskRhythm's remaining 1.0 step is the App Store submit lane itself — everything upstream of it is built and proven, which means the next thing standing between the app and a listing is me pressing the button.

On Build Studio, the Monitor tab currently knows about two things: CI and dependencies. The list of conditions that go red while nobody is looking is longer than that, and the design constraint I want to keep is the one that made this version work — every source has to be queryable for its current state, not a fire-and-forget event, or the auto-clearing behaviour stops being free and starts being reconciliation logic that drifts.

There is also something new in the studio that is not ready to talk about yet. In a couple of weeks.

If you are running Build Studio and the Monitor tab tells you something useful — or tells you something wrong — I would like to hear it. Open an issue, email [email protected], or find me at @MyNextStudio.