The Repository That Refused to Stage

A vendored repository broke git add, and a copied .env threatened to become part of the history. Both had to be fixed before a snapshot could be trusted.

The first version of the recorder assumed that git add -A does what it says. Then I pointed it at a project with a vendored repository inside it.

error: '<path>/' does not have a commit checked out
fatal: adding files failed

Git refuses to index a nested repository that has no commit checked out. A retry would not help. The history store has to survive one broken embedded repository, so Tachograph parses the quoted path out of Git’s error, appends it to the history repository’s info/exclude, and retries the add once. The rest of the worktree is recorded; the nested repository is not. That failure path is why Snapshot does not simply propagate the first non-zero exit.

The Quiet Version of the Same Problem

Secrets are the same shape of problem, only quieter. A history that records everything will eventually record a .env file. The history repository starts with /.git, /.jj, .env, .env.local, .env.*.local, *.pem and *.key excluded. That covers the default names, not the paths a lifecycle action creates after the first snapshot.

The copy rules therefore add every concrete path they write to info/exclude, and they do it while holding the repository lock. Writing .env first and excluding it afterwards leaves a window in which a snapshot can pick it up. The order is not a detail; it is the difference between a local convenience and a leaked credential.

What Staging Does Not Tell You

The second surprise was the opposite one: staging succeeded and still produced nothing to record. git diff --cached --quiet exits 1 when there are staged changes and 0 when there are none. A recorder that only checks the commit result cannot distinguish “nothing changed” from “the commit failed”. Tachograph asks first, returns ErrNoChanges, and does not create an empty snapshot just because the watcher fired.

The same question appears at a larger scale with ignored files. A worktree can change without producing a snapshot: the only modified file is ignored, or the change is a mode Git normalizes away. The recorder treats that as a no-op, not an error. The UI shows the last real snapshot; it does not pretend that every event produced a commit.

A History Store Has to Be Boring

The pattern behind both failures is that the history store is not allowed to have opinions about the project. It cannot require every nested repository to be healthy, and it cannot assume that a file the user wrote is safe to publish. It records what it can, excludes what it must, and says so in the log.

That is also why the exclusions live in the history repository, not in the project’s .gitignore. The project’s ignore rules are about the project. The history store’s rules are about what a snapshot is allowed to contain. Mixing them would make the recorder depend on a file it is supposed to leave alone.

The next problem was not staging at all. It was what happens when a history becomes old enough to need thinning—and what that rewrite does to every hash the rest of the system had learned to trust.


A history store records; it does not judge. Every rule it adds about what it will accept is a rule that can silently lose work, so an exclusion earns its place twice: once by protecting a secret, once by leaving the rest of the worktree recordable.

AI Insights: The Store Has No Opinions
Source Code: MPL-2.0 · Content: CC BY-SA 4.0 · Credits & AI Disclosure