The Repository That Refused to Stage
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.
