CI & Breaking Changes
Gates that stop a bus-breaking rename from reaching main, plus conflicts and troubleshooting.
A type bus without CI gates is a type bus that breaks someone else's build on a Friday. Two gates on the backend, one on the frontend.
Backend gate 1 — validity
- run: npx chowbea-axios extract --checkRuns the full extraction and validation without writing anything. Non-zero exit on any violation, every violation printed with file:line. This catches closed-world breaks, duplicate names, and non-type exports before they merge.
Backend gate 2 — breaking changes
- run: npx chowbea-axios extract --check --diff "$STAGING_BUS_URL" --fail-on-removedCompares the manifest this PR would produce against the one staging is currently serving:
bus: + StudentId
bus: ~ GradeMap
bus: - LegacyGrade (removed)
Type bus removed 1 type(s) present in the baseline — failing (--fail-on-removed).Removals are the dangerous edit: every frontend importing that type stops compiling. Renames show up as remove + add, which is honest — to a consumer, a rename is a removal.
Drop --fail-on-removed if you want the report without the gate — useful early on, when the bus is still churning.
The baseline can be a URL or a local file. HTTP baselines get a 30-second timeout, so a hung staging box fails your job in seconds instead of burning a runner slot.
Frontend gate — staleness
If your repo commits _generated/, the scaffolded workflow already covers the bus: fetch syncs both the spec and the bus, so the existing "generated code is out of date" check sees stale bus files too. No extra step.
- run: npx chowbea-axios fetch
- run: git diff --exit-code -- src/api || (echo "Generated code is out of date!" && exit 1)No drift by construction
There's no committed manifest to fall out of sync. extract runs during the build, from the source being deployed, so the served manifest always matches the deployed code. The failure mode where a stale artifact is served for weeks simply doesn't exist here.
Merge conflicts
Bus output lives under _generated/, so the existing tooling covers it:
chowbea-axios resolveConflicted files under the generated directory are regenerated from the spec and cached manifest, then staged. Don't hand-merge generated files — you're editing a projection, and the merge result is meaningless.
Generated bus files carry no timestamps or counters, so the conflict surface is minimal to begin with: two branches that added different types produce a clean regeneration, not a fight over a header line.
Troubleshooting
ENOENT from readBusManifest() — the manifest isn't in the process's working directory. Run extract, and add it to your build script so deploys always carry it. In file mode (busHandler()), you get a 503 with a hint instead of a crash.
Frontend says "unchanged" but you just changed a type — the manifest hash didn't move, which means extract didn't re-run. Check that extract --watch is running, or that your build actually ran prebuild. In static mode (busHandler(readBusManifest())), also restart the server — static mode snapshots at boot.
Unsupported chowbea bus manifest version — the backend's CLI is newer than the frontend's. Upgrade the frontend CLI.
Closed-world errors you don't understand — the message names the referencing type, the referenced type, and the exact file and line where the referenced type lives. Add that type to a barrel or tag it, then re-run. Repeat until quiet; the errors are the closure algorithm.
extract finds nothing — it uses the nearest tsconfig.json and only sees files that config includes. If your barrels live outside include, or your build uses tsconfig.build.json, pass --project.
Types outside rootDir — a *.chowbea.ts file above your tsconfig's rootDir produces an invalid key and is rejected. Move it under the root, or widen rootDir.