AI Killed Git Commits: So I Stopped Publishing Them
Today I shipped contenox 1.0.0. Not by pushing a tag on top of a thousand commits, but as a single commit into an empty repository: the whole tree, one signed tag, binaries built from that tag by CI. The 957 commits that got me there are still public, in the old repository, as history. They are no longer how the project is published. This post is about why, and about what went wrong before I had finished reading the result back. What a commit used to mean GitHub's workflow rests on four assumptions so old that nobody states them any more: - A commit is a unit of human intent. Someone decided something and typed it. - A pull request is a unit of review. A human reads the diff, because a human wrote it. - History is provenance. Who changed what, when, and - through the message - why. - Timestamps are labor. The contribution graph on your profile is a diary. All four were true in 2008. For a tree that agents write, none of them survive contact. What my repository actually looked like Some numbers from a tree you can inspect yourself: - 957 commits in just over a year, most of them named Checkpoint, Fix tests, Snapshot WiP. Dozens on a busy day. - The production Go grew from 17,267 hand-written lines to 134,040 agent-assisted ones. Measured, not estimated. The median file stayed the same size; the number of files and packages did not. - At one point 530 uncommitted paths sat in a single working tree. Inside that blob, the file that carried the repository's own conventions had been deleted. Nobody noticed for days, because nobody reviews a 530-file diff. A commit stream like that is not history. It is a log. Reading it tells you nothing about what a human decided - the decisions happened in prompts, in agent declarations, in a policy file - and it tells you one thing with great precision: when the work happened. If you also do client work, a public commit stream is a timesheet you never agreed to publish. Review had quietly inverted, too. I was no longer reviewing commits. I was reviewing outcomes: does it build, do the tests pass, does the binary do the thing. The commit boundary had become an accident of when an agent stopped. Source drops So the unit of publication moved to the only boundary that still meant something: the release. The tree lives in a private monorepo, next to the commercial parts of the product. The public repository, contenox/contenox , is a release mirror: - main moves only by release. Every commit on it isRelease vX.Y.Z and carries the complete tree. The diff between two commits is the diff between two releases - readable in one sitting, which a month of checkpoints never was. - Every tag is signed, and its annotation is the release notes. CI builds the four binaries from the tag, writes checksums, attests provenance, publishes. - Issues stay open. Pull requests are welcome and are carried upstream by hand. The author gets Co-authored-by on the release commit and a line in the notes. The branch is evidence of the change, not its delivery vehicle. The mechanism is a shell script shorter than this post. The part that matters: # export exactly what git tracks under the OSS tree: no ignored files, no build outputs git ls-files -z -- runtime | tar --null -T - -cf - | tar -xf - -C "$MIRROR" --strip-components=1 # refuse to publish anything that matches a list of private markers grep -rIlE -f private-patterns.txt "$MIRROR" && exit 1 git -C "$MIRROR" add -A git -C "$MIRROR" commit -F release-notes.md # "Release v1.0.0", signed git -C "$MIRROR" tag -a -F release-notes.md v1.0.0 # signed; the notes ride in the annotation git -C "$MIRROR" push origin main v1.0.0 # release.yml takes it from here Nothing here is new. Android has shipped AOSP as source drops for years, and plenty of vendors publish the same way. What is new is the reason: not secrecy, but that the intermediate commits stopped carrying information worth publishing. What I got wrong on day one The first drop shipped with a CONTRIBUTING.md that described, in some detail, the workflow I had just built to keep my workflow out of the public repository. An agent wrote it - thoroughly, correctly, and against the brief - and I did not read it, because it was one of twenty-nine files in the commit that landed the whole mechanism. Then the Go module proxy fetched v1.0.0, and from that moment the text was permanent: you cannot re-tag a Go module without handing every later go get a checksum mismatch. Two lessons, neither of them about tooling. First: every gate I had was mechanical - lint, tests, a scan for secrets and private paths - and not one of them was a human has read what is about to become permanent. The scan catches credentials. It cannot catch a paragraph of honest prose. Second, and this one took me a minute: my reflex was to ship v1.0.1 fifteen minutes later. That reflex is exactly what the new repository exists to end. A workflow that makes publishing effortless will be used effortlessly. So the rule is not a script; it is a calendar. The public repository moves once a month. The first proof that the regime changed is a month of silence after v1.0.0 - imperfect CONTRIBUTING and all. What this does not solve - There is no public CI. The gates run before the drop, in private. What the public repository offers instead is a signed tag, a build recipe you can read, checksums and provenance. If a green badge on every push is what you trust, this model asks you to trust something else. - Contributors do not get their commit on a branch. They get credit on a release. Some people will hate that. I understand. - It looks like how proprietary vendors publish. It is. The license is Apache-2.0, the cadence is declared, and the old history is public. That is the whole difference, and I think it is enough. - It does not make the code reviewable. It makes the releases reviewable. What happens to the code between releases is a different problem - and, honestly, the larger one. What I would ask GitHub for A repository mode. Call it release drop: main moves only by tag; history is one commit per release; pull requests are proposals, not merges; provenance is attested per release, not per push. The platform already has every piece - immutable releases, build attestations, environments with required reviewers. It just still assumes a human typed the commit. Until then: a shell script, a calendar, and a rule that nothing becomes permanent before a person has read it. contenox 1.0.0 is at github.com/contenox/contenox. The history that got it there is at github.com/contenox/contenox-v0. The measurements are in the Lab: contenox.com/docs/rnd/handwritten-vs-generated/. Top comments (0)
Comments
No comments yet. Start the discussion.