
A static site that several AI agent sessions edit: one --check gate before every commit
Nobody edits generated files, and before every commit --check must report zero files a rebuild would change and zero gate errors. With those two rules, budget overruns and drifting dates that only appear once work is combined get caught before a commit.
python tools/build.py --check failed on the night of October 19, 2026, right after a rebase onto another session's commit, with one line: [BUDGET-FONT] index.html: font files this page needs 421 KB > 400 KB. The build before the rebase had no error on the home page, so the problem only showed up once the two pieces of work were combined. This site is built without a framework by six Python modules (3,616 lines) that import nothing outside the standard library, and the rule is that whoever commits, person or agent, passes that command first. This note follows four of the times it stopped a commit between October 5 and 22.
Sources: this site's git history (bdbbe97, b82faac, 663f04e, 6b96643, 4f46df8 and others), the code in tools/, the review documents written against the redesign spec, work session logs and the AX notes publishing log. Line, test and file counts were redone at commit 1d5d432 on October 24, 2026.
Contents (10)
- Four times --check stopped a commit
- Humans and agents write only the body and the data files
- Test idempotency with tomorrow's date, not with a second build
- At the first integration, the gate was the one that was wrong
- Another note's title can break a page's font budget
- Share images use real screens and never carry text
- Not for browser editing, very large sites or repositories without CI
- Adding a --check gate to another site, in order
- What we will check next
- Frequently asked questions
Four times --check stopped a commit
--check rebuilds the whole site in memory and asks two questions: does any output differ by even one byte from the file on disk, and did the checker module tools/checks.py report an error? Either one means exit 1. In outline:
outputs = build_all() # pages, feeds, sitemap, CSS: in memory
changed = [p for p, text in outputs.items()
if read_file(p) != text]
report = checks.run(ROOT, outputs) # 106 issue codes
if "--check" in argv:
if changed or report.errors:
exit(1) # nothing is written
else:
write_files(changed)
On October 24 the build wrote 162 files, the checker had 106 issue codes (fixed names such as SD-CRUMB-HOME), and one run took 11.6 seconds on this PC.
The four stops: two files out of date right after the first build on October 5; 101 gate errors when the redesign was integrated on October 18; the home page needing 421 KB of fonts against a 400 KB cap after the October 19 rebase; and on October 22, a new note title that pushed two older notes to 482 KB and 473 KB against a 470 KB cap. On October 23 we added a check that halts the image tools on characters the font draws as empty.

Humans and agents write only the body and the data files
Every file has exactly one kind of writer. Condensed from the table in the repository's AGENTS.md:
| Writer | Files |
|---|---|
| Content authors (people or agents) | The <title>, description and <main> of hand-written pages (minus build blocks), data/apps.json, data/ax.json |
| One build-owner session | tools/*.py, and it runs the integration build |
| The build only | Head, header, footer, breadcrumbs, JSON-LD, home and list pages, topic pages, sitemap, RSS, robots.txt, llms.txt, the dates file data/content-dates.json, two generated stylesheets |
Inside a hand-written page, every spot the build fills is wrapped in a marker such as <!-- build:meta -->. Each build empties and refills those blocks and leaves the rest of <main> byte for byte, with one exception: the tracking parameter on Play install links. Content authors never run the build. They run the read-only python tools/checks.py --file <path>.
The first plan did not have this split; a review added it. An agent wrote the October 18 redesign spec, and it told every work package both to finish with build and --check and to leave generated HTML alone. A second agent, assigned to attack the spec, pointed out the contradiction: the build rewrites the head and footer of every hand-written page, so one session building to verify its work would change some 60 files another session was editing. Since then the integration build runs once, in one place.
Rebase conflicts follow the same rule. On October 19 the conflicted files were ones the build rewrites: 404.html, the about page, app pages, rss.xml. We took the other session's version of those, kept our own AX note files, and rebuilt. Do not hand-edit or hand-merge generated files. Pick a side, rebuild, and commit only when --check reports zero.
The six build modules import only standard library pieces such as hashlib, html.parser and xml.etree, so there is no package to install. The four image tools (775 lines) are different: they need Pillow, and they sit outside --check.
Test idempotency with tomorrow's date, not with a second build
On October 5 an agent wrote the first build script (725 lines when committed) and ran it. This is what came back:
45 files, 45 changed
out of date:
apps/sugarcard/index.html
sitemap.xml
A second build changed two files, and the third run said up to date. The lines that moved were sitemap lastmod dates.
There were two layers of cause. The near one was ordering: the build read lastmod from git status and git log before writing any file, so writing files changed the git state and the next build saw different input. The deeper one was where the date came from at all, which was repository state and the wall clock instead of page content.
The review agent flagged only the near cause, and that day's fix only addressed it: a set of "files this build writes get today's date". Two builds on the same day then agreed. The dates still came from git, though. A build that converges after two runs looks idempotent, yet the same input built the next day moves the dates again.
Thirteen days later the redesign removed that design. The redesign spec, also written by an agent, had two loops of the same kind. A generated app page kept its "last updated" line inside the hashed body, so the date changed the hash and the hash changed the date. And the spec had the build write a generated block into site.css while the stylesheet version string was a hash of that same file; the reviewer wrote that --check would "fail once, forever". Both were fixed before any code was written.
On the CSS side, the generated stylesheets are now built in memory first and the version string is computed and substituted before anything is written. The date rules (content hash, the SOURCE_DATE variable) are in Every sitemap lastmod had the same date: deriving modified dates from a content hash.
At the October 18 integration, sha256 hashes of 445 tracked and untracked files were taken after the first build, the second build and a SOURCE_DATE run, with zero differences. On October 24, in a scratch copy, a plain build, SOURCE_DATE=2026-10-25 and SOURCE_DATE=2026-12-31 all printed "162 files, 0 changed".
At the first integration, the gate was the one that was wrong
The October 18 redesign ran as a workflow split across 36 agents and took about 2 hours 24 minutes. The first --check on the merged result found zero files to rewrite and 101 gate errors.

Most of the 43 BUDGET-FONT errors were the checker's own mistake. English pages draw © in the system font through CSS, but the checker counted that character and concluded the pages needed extra font files. One cost was real: the breadcrumb separator › alone made every page with breadcrumbs download one more 25 KB font file. The fix aligned the checker's character counting with the CSS rules and moved the separator to the system font. The rest were 24 anchor texts pointing at two different URLs, 19 app-colour samples on the design demo page, 14 share images over 200 KB and one broken link.
When the gate changes, its tests change with it. That fix added six tests, and each of the eight new guards was removed in turn in a scratch copy to confirm a test failed. As of October 24 every one of the 106 issue codes appears by name in test_checks.py, out of 188 tests in total.
The integrating agent also made two gate calls on its own. Individual privacy policies and the design demo page have no font cap in the spec, so their overruns became warnings, and the cap on the generated font stylesheet went from 20 KB to 24 KB. The agent wrote "needs confirmation" for both in its report. The operator settled a different item from that same report that evening (listing the privacy officer under the business name). The comment on the 24 KB cap still says "the spec owner should confirm or lower it" on October 24.
Another note's title can break a page's font budget
The site's Korean font is split into 92 files by Unicode range, and a page downloads only the files that cover its characters. Note pages are capped at 470 KB, home and list pages at 400 KB. One rare Hangul syllable can pull in a 17 to 40 KB file by itself, so two of the first three AX notes came out at 619 KB and 533 KB. We reworded them that evening and wrote a helper that shows which characters pull in which files.
The home page broke for another reason. It listed the three newest AX note titles, which change every day, and after the rebase merged another session's work it needed 421 KB. Instead of rewording titles, the home list became links to topic pages (663f04e); topic names are fixed text, so the cost stops moving. The October 22 change that dropped note titles from the hub's topic cards had the same reason and took the hub from 468 KB to 389 KB (6b96643).
Also on October 22, a single syllable, 쟀, in a new note's title travelled through the related-notes lists into two older notes and pushed them to 482 KB and 473 KB. Checking the new file alone passes. Rewording the title brought the two pages to 450 KB and 441 KB.
Share images use real screens and never carry text
tools/og.py builds the share images for app pages and help articles from a real Play screenshot on a neutral background, with no text and no logo. Text cards are reserved for the home, hub, about, privacy and AX pages.
--check does not rebuild and compare images. Running og.py in a scratch copy on October 24 changed 4 of the 155 files: two app cards committed on October 19 with a new app page without going through og.py (they carry a title and tagline), and the two home cards, committed before that app's icon existed. Two og.py runs in a row produce identical bytes, so the generator is deterministic and the files that differ from its output are the committed ones. The daily publishing routine does not commit unrelated image changes, so from October 20 to 24 it ran og.py and restored these four files five times.
The background to the October 23 blank-glyph check in the image tools is in Android showed some Hangul as blank gaps: NanumSquare's cmap claimed 11,172 syllables but drew only 2,479.
Not for browser editing, very large sites or repositories without CI
Teams that write in a browser and expect a preview will not like it. Here authors edit the HTML body directly and run commands. We have no record of weighing it against a static site generator. But three of the four stops here (October 18, 19 and 22) came from checks over the whole output, so a generator would still need those checks and the date rules built separately.
Large sites pay more. --check rebuilds and compares everything on every run. 11.6 seconds for 162 files is fine, but there is no incremental build.
Nothing enforces the gate. The repository has no CI job, so it works only if someone runs the command before committing. The gate can also be wrong, as the 43 errors on October 18 showed, and it cannot stop a commit that raises its own caps.
Adding a --check gate to another site, in order
- Put a table of who writes which files at the top of your agent instructions (AGENTS.md or similar), and mark generated files as hands-off.
- Wrap every spot the build fills inside a hand-written page in
<!-- build:NAME -->markers; the build empties and refills them each time. - Make
--checkrender every output in memory, compare it with disk, run the checks on the same output, and exit 1 if either fails. - Derive dates and version strings from content hashes, and keep date lines and automatic lists out of the hash input.
- Test idempotency three ways: two builds in a row, a build dated tomorrow, and a build dated in the past (dates must not go backwards).
- Give every check a fixed code and a test that proves the code fires. Remove each guard in a scratch copy and confirm a test fails.
- Content sessions run only the read-only per-file check; one session runs the integration build once.
- When generated files conflict in a rebase, pick a side, rebuild and run
--check. - Check budgets that mix in other pages' text (fonts, size) on the whole output, and keep daily-changing lists off shared pages.
- Have a person review any commit that changes a cap.
What we will check next
Images are still outside the gate. The next check is a step in --check that compares og.py output with the committed files; before that, the text-bearing app card has to become either a listed og.py exception or generator output. The 24 KB stylesheet cap and the two warning-only pages have not had a human review yet. How several sessions shared this repository is described in "Use hundreds of agents" in practice: 10 workstreams, 59 commits in a day, three running at once.
Frequently asked questions
Do you commit the generated HTML?
Yes. GitHub Pages serves the repository's files as they are (with .nojekyll, Jekyll processing is off), so the build output is the deployment. --check confirms that the committed output equals what the current inputs produce.
How do you test that a static site build is idempotent?
Build twice in a row, then with tomorrow's date and with a past date, and expect zero changed files. Two same-day builds can agree while dates still move the next day, so make "today" overridable through an environment variable first.
How do you resolve conflicts when several AI agent sessions edit the same site repository?
Merge only source files such as page bodies and data; for generated files, pick one side and rebuild. Run --check on the rebuilt result and commit only when it passes. On this site, that step after a rebase is where the home page font overflow first appeared.
This note is written from values SugarMount measured itself; an AI coding agent was used to draft it.
More AX notes
- Structured data after FAQ rich results ended: what we kept for GEO
- Privacy policy AI processing location (cross-border transfer): a notice fixed twice in one day, then tied to code
- Every sitemap lastmod had the same date: deriving modified dates from a content hash
- Pre-gzipping Android assets, measured: zero APK savings, and .pack.gz became .pack with a FileNotFoundException
- A market:// link opens a store chooser on SKT-sold Samsung phones: set the Play package first
- A splash that never moved on: addOnCompleteListener(this) and onStop