jj Workflow
For daily-command tables, git equivalents, troubleshooting, parallel-experiment patterns, immutable-heads disable/restore commands, recommended config, and the full revset cheatsheet, see REFERENCE.md.
Philosophy
- Commits are cheap, descriptions are mandatory. The working copy is always a commit. Never leave it as "(no description set)".
- Experiment freely, the oplog is your safety net.
jj undoandjj op restoremake anything reversible. - Conflicts are state, not emergencies. jj records conflicts in commits as structured data; rebase still succeeds.
- Change IDs are your handle on work. Commit hashes change on rewrite; change IDs don't.
- Bookmarks exist for GitHub, not for you. Work with anonymous changes; add bookmarks only when pushing.
- Keep the stack shallow. Squash early.
- Use
absorbover manual squash routing. Let jj distribute hunks to the right ancestor. - Colocated = invisible to the team. Teammates see standard git.
CRITICAL: AI-specific rules
Always pass -m — unset opens an editor and blocks the agent:
jj new -m "msg"
jj describe -m "msg"
jj commit -m "msg"
jj squash -m "msg"
These open an editor and hang the agent (jj 0.39). Two guards enforce this —
hooks/jj_interactive_guard.sh blocks them pre-run, and $JJ_EDITOR
(hooks/jj-reject-editor.sh) fail-fasts any editor jj still opens — but get
them right the first time:
jj describe/jj commitwith no-m→ add-m "msg"jj squashwith no message → add-m "msg", or-uto reuse the destination'sjj commit/jj squashwith-i/--interactive/--tool→ opens the hunk picker even with-m; drop the flag, edit files, thenjj squash -mjj split,jj diffedit→ no non-interactive mode; restructure withjj squash -m/jj new -mjj resolve→ edit the conflict markers in the files, thenjj squash -m; or pass--tooljj config edit→ usejj config set <name> <value>
Renamed: untrack a file with jj file untrack <path> — jj forget is gone.
Core concepts
- Working copy = commit. Every file edit is tracked in
@. No staging area, nogit add. @= current change,@-= parent,@--= grandparent.- Change IDs (e.g.
kpqxywon) are stable across rewrites. Use these, not commit hashes. - Conflicts are state, not emergencies — jj records them in commits and rebase still succeeds.
- Previous versions:
<change-id>/0(latest),/1(previous).jj restore --from xyz/1 --to xyzreverts to a prior state.
Workflows
Squash (recommended)
jj describe -m "feat: what I'm building"
jj new -m "wip"
# ... make changes ...
jj squash -m "feat: done"
Commit (simpler)
jj commit -m "feat: what I did" # = describe + new
Edit (mid-stack fix)
jj edit <change-id>
# ... fix ...
jj new -m "back to work" # descendants auto-rebased
Absorb
From @, jj absorb routes each hunk to the ancestor where those lines were last modified. Use instead of manual squash routing when fixing across a stack.
Bookmarks & pushing
Bookmarks don't auto-advance — move them explicitly. @ is typically empty; target @-.
jj bookmark set <name> -r @-
jj git push
Don't rewrite reviewed PR history
If a PR has review comments, do NOT squash or rewrite the original commits — review threads detach from line anchors. Add new commits on top instead, and only rewrite after Teej confirms. See rules/version-control.md.
Default (safe — preserves comment anchors):
jj new feature-x- -m "address feedback"
# ... make changes ...
jj describe -m "fixup: address feedback"
jj bookmark set feature-x -r @-
jj git push
Creating PRs (jj + gh)
In jj-colocated repos, git HEAD is detached — gh pr create fails with "not on any branch". Always pass --head <bookmark> — never rely on git auto-detection:
jj bookmark set feature-x -r @-
jj git push
gh pr create --head feature-x --title "..." --body "..."
Recovery
jj op log
jj undo
jj op restore <id>
jj evolog [-r <change-id>]
Immutable commits
Pushed commits are protected by immutable_heads(). Always ask Teej before disabling protection — rewriting remote bookmarks means force-pushing shared history. See REFERENCE.md for the disable/restore commands.
Revsets
jj log -r 'trunk()..@' # everything between main and here
jj log -r '::@ & ~::trunk()' # my branch only
jj log -r 'author("trevor")' # my commits
Full cheatsheet in REFERENCE.md.