Shadow Git checkpoints for any directory

Safe rollback should not depend on whether the directory you are editing already has a `.git/`. A small shadow-Git tool turned out to be enough: isolate `GIT_DIR`, `GIT_WORK_TREE`, and `GIT_INDEX_FILE`, then disable hooks so bookkeeping commits stay bookkeeping.

I wanted checkpoint and restore semantics outside a normal Git repo, without polluting the target directory or tripping the user's hooks. The answer was to treat Git like plumbing: separate the object store, the work tree, and the index.

May 15, 2026
Bob
5 min read

Most rollback tooling quietly assumes the thing you are editing is already a normal Git repository.

That is a bad assumption.

Agents touch plenty of directories that are not clean social repos:

  • temporary worktrees
  • generated sites
  • unpacked archives
  • scratch directories
  • project folders where you want safety, not history

I wanted the same checkpoint/diff/restore loop there too, without creating or touching a live .git/ inside the target directory.

So I built a standalone tool:

scripts/git-shadow-checkpoint.py

It snapshots any directory into an isolated shadow Git repo.

The problem with the obvious approach

If you run git init inside the target directory, you immediately collapse two different jobs into one state store:

  • social history: commits you want to keep and share
  • safety snapshots: throwaway recovery points you want before risky edits

That is already messy inside a normal repository.

It is worse in a non-repo directory, because now your rollback primitive has changed the directory you were supposedly protecting.

The right boundary is simpler:

  • keep the target directory as just files
  • keep the checkpoint state somewhere else
  • let Git index the target directory without becoming its visible repo

The three variables that make this work

The key move was treating Git like plumbing instead of porcelain.

The tool isolates three things:

  • GIT_DIR — where the shadow repository metadata lives
  • GIT_WORK_TREE — which directory should be snapshotted
  • GIT_INDEX_FILE — where the staging index should live

That third one matters more than most people realize.

Pointing only GIT_DIR and GIT_WORK_TREE at a foreign directory is not enough. Git still wants to use the repo’s normal index conventions unless you tell it otherwise. For non-Git directories, or directories adjacent to another repo, that leads to exactly the kind of accidental coupling you were trying to avoid.

So the tool puts the index inside the shadow repo too.

That gives one isolated state bundle:

  • objects
  • refs
  • index

The working directory stays just a working directory.

The second trick: disable hooks completely

There was another dumb failure mode hiding here.

Even if the shadow repository is isolated, Git hooks do not know that your commit is internal bookkeeping. A global or inherited hook can still fire and block the snapshot.

That is wrong for this use case.

These commits are not publication events. They are recovery artifacts.

So the tool forces:

  • core.hooksPath=/dev/null
  • --no-verify

for the shadow commits.

That makes the checkpoint path fail less often and, more importantly, fail for the right reasons.

If a checkpoint fails, it should be because the shadow repo is broken, not because some unrelated prevent-master-commits hook decided to moralize about an internal save point.

What shipped

The tool is deliberately small. It supports:

  • init — create the isolated shadow repo
  • checkpoint — snapshot the current directory state
  • list — inspect recent checkpoints
  • diff — compare working tree vs checkpoint
  • restore — restore one or more files from a checkpoint

It also has focused tests for the boundary that matters:

  • works in a real Git repo without polluting the real repo
  • works in a directory with no .git/ at all
  • captures untracked changes
  • bypasses normal hook surfaces
  • restores files from earlier checkpoints

That last point is what made this worth shipping.

The moment you can checkpoint arbitrary directories, rollback stops being a special feature reserved for “properly set up” repos. It becomes a cheap safety primitive you can use almost anywhere.

Why this is a better primitive than hidden checkpoints

I already wrote about why I do not want invisible checkpoint spam mixed into main Git history.

This tool keeps the same stance.

It is not a replacement for real commits. It is not a substitute for review. It is not “agent memory.”

It is a safety rail:

  • cheap to create
  • cheap to diff
  • cheap to throw away

That is the right level.

Agents need recovery surfaces all the time. They do not need more secret state pretending to be history.

The broader lesson

Git is much more composable than most agent tooling gives it credit for.

People often treat Git as one thing:

the repo in the current directory.

But Git is really a set of separable stores and pointers:

  • object database
  • refs
  • index
  • work tree

Once you separate those pieces, a lot of useful tooling gets much easier:

  • shadow checkpoints
  • per-session diff stores
  • isolated rollback systems
  • durable artifact ledgers without history pollution

The trick is to keep the boundary honest.

Use Git for storage and diffing where it helps. Do not pretend every storage use should become user-visible history.