Jujutsu (jj) Usage Guide for Claude Code
Use jj (Jujutsu) instead of git for version control. jj is Git-compatible
with a simpler model and powerful history editing.
Delegate noisy work to a subagent
For multi-step VCS work — history cleanup, rebase, conflict resolution,
splitting commits — spawn a subagent (Sonnet) to do it and return a one-line
summary. This keeps verbose jj log/diff/op log output and this reference
out of the parent's context, saving tokens. Tell the subagent exactly what
changed and what the end state should be.
Do NOT delegate trivial one-shots (jj describe -m … && jj git push) — spawn
overhead exceeds the saving. Run those inline.
Mental model
- No staging area — every change is part of the working copy commit.
- Working copy is a commit —
@is your current working copy commit. - Bookmarks, not branches — commits are anonymous; name a bookmark only to push.
- Conflicts are values — they don't block operations; resolve them later.
- Automatic rebasing — descendants rebase automatically when you edit history.
| Git | jj |
|---|---|
branch |
bookmark |
HEAD |
@ (working copy) |
checkout |
edit / new |
stash |
not needed — just jj new |
commit --amend |
just edit @, changes auto-apply |
Command reference
| Action | Command |
|---|---|
| Status | jj st |
| Log | jj log (jj log -r 'all()' for everything) |
| Diff working copy / a commit | jj diff --git / jj diff --git -r <rev> |
| Show a commit | jj show <rev> |
Describe @ |
jj desc -m "feat: …" |
New commit on @ / on a rev |
jj new [-m "…"] / jj new <rev> |
| Edit an existing commit | jj edit <rev> (descendants auto-rebase) |
| Squash into parent | jj squash -m "…" (-r <rev> for a specific one) |
| Squash paths across commits | jj squash --from <src> --into <dst> <paths> |
| Restore paths from a rev | jj restore --from <rev> <paths> |
| Split by file | jj split <path>... (--parallel for siblings) |
| Absorb into ancestors | jj absorb (see caveat below) |
| Rebase | jj rebase -d <dest> (-r one, -s +descendants, -b branch) |
| Bookmarks | jj bookmark create|set|delete <n> · jj bookmark track <n>@origin |
| Fetch / push | jj git fetch / jj git push -b <n> |
| Conflicts / undo | jj log -r 'conflicts()' / jj op undo |
Workflow
Atomic commits by construction. The cheapest path is to create commits as
you go, not to split afterward. Start work with a description in place and run
jj new between logical steps:
jj new main -m "refactor: extract auth helper" # start from main
# ...edit...
jj new -m "feat: use helper in login flow"
# ...edit...
Adjust later with jj describe -m "…"; merge two commits with jj squash -m "…".
Before push:
jj log -r 'conflicts()' # 1. no conflicts
jj st # 2. clean status
jj git fetch && jj rebase -d main # 3. onto latest main
jj log -r '::@ ~ ::main' # 4. review your changes
jj bookmark create <n> && jj git push -b <n> # 5. push
Update a PR after review: jj edit <commit>, make changes (descendants
auto-rebase), jj new <tip> to return to the tip, jj git push -b <n>.
Bookmark naming: feat/user-dashboard, fix/issue-123-auth-bug,
claude/<feature>-<session-id>.
Judgment notes
jj absorbonly moves hunks into ancestor commits that already touch the same lines. It won't create commits; split mixed unrelated changes in@first.- Recovery:
jj op logshows every operation;jj op undoreverts the last,jj op restore <id>jumps to a known-good state. Nothing is ever truly lost. - Colocate with git:
jj git init --colocateshares the.gitdir, sojj git fetch/pushkeep both in sync and git commands still work for edge cases.
Avoiding interactive editors
LLMs can't drive TUIs, so always use non-interactive forms:
- Inline messages — pass
-m "…"todescribe,squash,commit,new.jj splittakes-m "first" -m "second"for both halves. - Squash / restore specific paths without an editor — the
--from/--intoand--from <rev> <paths>forms above. - Split by file —
jj split <path>...opens no editor when boundaries align with files. Prefer it over the hunk-level TUI.
Hunk-level split within one file (no file boundary to use):
- Split before you mess up —
jj new -m "next thing"between steps; you never needsplit. - Compose —
jj new; edit@to part A's state;jj squash --from <original> --into @ <paths>; the original now holds only part B. - Patch round-trip (last resort, scriptable):
jj diff -r <rev> --git > /tmp/full.patch # hand-split into partial/rest jj restore -r <rev> && jj new -r <rev>- git apply /tmp/partial.patch && jj commit -m "part A" git apply /tmp/rest.patch && jj commit -m "part B"