Avoid Losing Work with Jujutsu (jj) for AI Coding Agents

Someone in the Indy Hackers Slack shared the following post:

My first Gemini CLI experience: "I messed up. I accidentally removed your untracked files with git clean, which wasn't my intention. I'm truly sorry. Since they weren't in git, I can't restore them easily."

— rands (@rands@mastodon.social) View on Mastodon

I had also run into this problem while coding with AI agents. This seemed like an opportune time to write up some thoughts to hopefully save others some time and agony.

Sometimes this issue happened to me because I get too confident in the current work direction and have not committed in a while. And then one or two prompts later, I have a broken or confusing set of changes.

Or, as Rands's post mentions, sometimes the AI agent purposely or accidentally reverts some changes and can't recover them. Several versions ago, when Claude Code compacted context, it would also clear the terminal, so if it couldn't remember the changes, then I would be unlikely to recover.

Enter jj

To try to combat this problem, I've been setting up and using Jujutsu (henceforth jj) instances for the repos that I have. jj keeps track of every filesystem change in your repo. So you can use its history to see and restore changes that might have otherwise gotten lost. It also works fairly seamlessly with git, which basically all of my projects use at this point. It also doesn't affect other users of your projects. So in my mind there are basically no downsides to setting it up.

It's easy to set up jj alongside an existing git repo with jj git init --colocate.

Then there are a few commands that I've been using. I just set it up on my blog repo, and have the following info:

$ jj
@  lrklqzxy panozzaj@gmail.com 2025-11-22 16:06:25 b76e8471
│  (no description set)
◆  qppwxvzp panozzaj@gmail.com 2025-11-06 10:57:53 master master@origin git_head() e9476b33
│  Add site perf audit document
...
$ git log --oneline --graph --decorate | head 1
* e9476b3 (HEAD -> master, origin/master, origin/HEAD) Add site perf audit document

So you can see from the jj command output that it knows about git commit e9476b3 (jj's identifier for that commit is qppwxvzp).

One thing I almost immediately picked up on is that jj commit identifiers use characters in the range of [g-z], which is nice since git uses the hex characters ([0-9,a-f]) for its commit hashes. So this makes it easier to not mix up the two systems' unique identifiers. jj also highlights the unique starting characters in a different color, so it's easy to type out a couple of characters:

jj screenshot showing color-coded commit identifiers

From above, jj has a working set of changes that it currently describes with lrklqzxy/b76e8471. Or, at least it did, until I just saved this draft. Now the git identifier is something else, since the hash of the underlying filesystem changed.

The uncommitted set of changes is aliased to @. It's similar to the git working directory, but each filesystem change is actually "committed" under the hood. To save the current set of changes in jj and change @s identifier, we'd use jj describe to write a commit message.

Here's where the work-saving ability comes in. With jj, we can view the last couple of repo changes:

$ jj obslog --revision @ --patch --limit 2
@  lrklqzxy panozzaj@gmail.com 2025-11-22 16:16:46 2583d144
│  (no description set)
│  -- operation 213466f024c9 snapshot working copy
│  M _drafts/using-jujutsu-jj-with-ai-coding-agents.markdown
│  Modified regular file _drafts/using-jujutsu-jj-with-ai-coding-agents.markdown:
│      ...
│    37   37: * e9476b3 (HEAD -> master, origin/master, origin/HEAD) Add site perf audit document
│    38   38: ```
│    39   39:
│    40     : So you can see from the `jj` command output that it knows about git commit `e9476b3` (`jj`'s version of this is `qppwxvzp`), and has a working set of changes that it currently describes with `b76e8471`. Or, at least it did, until I just saved this draft. Now it's something else. So that working set is nicknamed `@`, and it's a pretty useful concept.
│         40: So you can see from the `jj` command output that it knows about git commit `e9476b3` (`jj`'s version of this is `qppwxvzp`).
│         41:
│         42: One thing I almost immediately picked up on is that the `jj` commit descriptors uses characters in the range of `[g-z]`, which is nice since `git` uses the hex characters (`[0-9][a-f]`) for its commit hashes. So this means that you'll never get the two systems' identifiers mixed up. It's not visible here, but `jj` also highlights the unique starting characters in a different color, so it's easy to type out a couple of characters.
│         43:
│         44: From above, `jj` has a working set of changes that it currently describes with `lrklqzxy`/`b76e8471`. Or, at least it did, until I just saved this draft. Now the git portion of it is something else, since the hash of the underlying filesystem changed. The uncommitted set of changes is nicknamed `@`, and it's a pretty useful concept. Similar to the `git` working directory, but each filesystem change is actually "committed" under the hood.
│         45:
│         46: To see this, we can see the last few changes:
│         47:
│    41   48:
│    42   49:
│    43   50:
│      ...
○  lrklqzxy hidden panozzaj@gmail.com 2025-11-22 16:10:37 1915095f
│  (no description set)
│  -- operation 217810a349cf snapshot working copy
│  M _drafts/using-jujutsu-jj-with-ai-coding-agents.markdown
│  Modified regular file _drafts/using-jujutsu-jj-with-ai-coding-agents.markdown:
│      ...
│    37   37: * e9476b3 (HEAD -> master, origin/master, origin/HEAD) Add site perf audit document
│    38   38: ```
│    39   39:
│    40   40: So you can see from the `jj` command output that it knows about git commit `e9476b3` (`jj`'s version of this is `qppwxvzp`), and has a working set of changes that it currently describes with `b76e8471`. Or, at least it did, until I just saved this draft. Now it's something else. So that working set is nicknamed `@`, and it's a pretty useful concept.
│    41   41:
│    42   42:
│    43   43:
│      ...

So this command (jj obslog --revision @ --patch --limit 2) basically says:

  • show me the operations (obslog)
  • starting with revision @ (--revision @)
  • show diffs (--patch)
  • limit to the last two changes (--limit 2)

Finally, when you make changes to the git repo, jj is kept in sync with it.

Long-time users of git might see this as being similar to the git svn bridge for Subversion. You get the advantages of working with git, but Subversion stays the source of truth for collaboration.

Advantages of using

I started using jj before Claude Code had its checkpointing or rewind feature. I still use this regularly since:

  1. If you get out of the context window, you can still see past changes (which I am not confident rewind handles correctly, and as previously mentioned, at one point my console history would clear when context was compacted)
  2. It works across editors / agents, so you don't need to rely on them implementing checkpoints. Also, if I accidentally lose a file using sed or mv or something, I could likely get it back correctly.
  3. You don't need to be an expert with jj to use it. If you get in trouble, your agent should know enough to use jj to get those changes back if asked, and otherwise seems to not be trained to use it, so it won't be committing there.

I don't yet fully grasp all of the underlying concepts to be able to use advanced jj correctly, but for this one case (recovering uncommitted changes that I actually wanted) it's saved me a couple of times. And it's just kind of a cool tool.

I suspect that there are things that I could do with using this to tracking incremental changes in jj and then batching those up for git commits. Almost like git add --patch and committing, or by saving semantic changes along the way. (Maybe this would be a useful place for the agent to track detailed change history?)

References

I think I originally got the idea of using jj for this purpose on some Hacker News comment, possibly this one:

Eh, I can see how, if you use GitButler, the porcelain is fairly irrelevant to you, but a few days ago I decided to try Jujutsu, asked Claude how I could do a few things that came up (commit, move branches, push/pull to Github). It took me ten minutes to become proficiend in Jujutsu, and now it's my VCS of choice.

I still use Lazygit for the improved diffing, but, as long as you don't mind being in detached HEAD all the time, there's really no issue with doing that. JJ interoperates fine with git, but why would I use the arcane git commands when JJ will do the same thing much more straightforwardly?

Also, the ability to jump from branch to branch with all my uncommitted files traveling with me is a godsend. Now I can breeze between feature development, bug fixing, copy changing, etc just by editing the commit I want. If I want multiple AI agents working on that stuff, I just make a worktree and get on with it.

Not to mention that I am really liking the fact that I can describe changes (basically add commit messages) before I'm done with them, so I can see them in the tree.

JJ is just all around great.

Although a newer comment also captures this idea, and is perhaps better documented:

With jj, every file change is automatically captured (no manual commits needed), and you can create lightweight "sandbox" revisions for each Claude Code task. When things go wrong, jj undo instantly reverts to any previous state. The operation log tracks everything, making it virtually impossible to lose work.

The workflow becomes: let Claude Code generate messy experimental code → use jj squash/jj split to shape clean commits afterward. You get automatic checkpointing plus powerful history manipulation in one tool.

I've been using jj with Claude Code for months and it's transformed how I work with coding agents - no fear of breaking things because everything is instantly reversible. The MCP integration seems like added complexity when jj's native capabilities already handle the core problem.

For anyone interested in the jj + agent workflow, read my post: https://slavakurilyak.com/posts/use-jujutsu-not-git

Categories: main

« How I Generally Reduced Sugar Consumption

Comments