| name | jj |
| description | Use when working with Jujutsu (jj) version control system, creating changes, managing bookmarks, or pushing code. Triggers on jj commands and .jj/ directory presence. |
| allowed-tools | Bash, Read, Grep, Glob, Edit, Write |
| metadata | {"filePattern":".jj/**","bashPattern":"\\bjj\\b"} |
jj (Jujutsu VCS) Guide
When a .jj/ directory is present, the repository is managed by jj. Use jj commands for all version control operations. Never use raw git commands.
Communication Convention
Use jj commands but speak in git terminology. Since the canonical remote is typically a git repository (GitHub/GitLab), use familiar git terms when communicating:
| jj term | Say this instead |
|---|
| change | commit |
| bookmark | branch |
@ (working copy) | current commit / working copy |
@- (parent) | parent commit |
| describe | set commit message |
This keeps communication clear for all team members regardless of their jj familiarity.
Critical Safety Rules
Never use raw git commands
In a colocated jj+git repository, raw git commands bypass jj's operation log and can corrupt repository state. The following are forbidden:
git commit — use jj describe + jj new
git add — not needed, jj auto-tracks all changes
git checkout — use jj edit or jj new
git branch — use jj bookmark
git stash — not needed, just jj new
git merge — use jj new commit1 commit2 (merge commit)
git rebase — use jj rebase
Exception: gh CLI is acceptable (it uses git internally for PR operations).
Always create a new commit before editing pushed code
If the current commit (@) has a branch that has been pushed to the remote, do not edit files directly. This rewrites history and causes force pushes.
Instead:
- Run
jj new to create a fresh empty commit on top
- Then make edits
Always pass -m to commands that open an editor
Commands like jj squash, jj split, and jj commit open an interactive editor (vim) to compose a message when no -m flag is provided. Claude Code runs in a non-interactive terminal, so the editor fails with Error reading input, exiting... and the command aborts.
Always pass -m "message" to these commands:
jj squash --from <rev> --into <rev> -m "combined commit message"
jj squash -m "squash into parent"
jj squash --from <rev> --into <rev>
jj squash
Always verify a bookmark exists before pushing
jj git push only pushes bookmarks. If the current commit has no bookmark, nothing gets pushed.
Before pushing:
- Check for a bookmark:
jj log -r @ --no-graph -T 'bookmarks'
- If none exists:
jj bookmark set <name> -r @
- Then push:
jj git push --bookmark <name>
Command Reference
| Git operation | jj command | Notes |
|---|
git status | jj status | Shows working copy changes |
git diff | jj diff | Diff of working copy |
git show <commit> | jj diff -r <rev> | Shows what a specific commit changed |
git diff --staged | Not needed | No staging area in jj |
git log | jj log | Shows change graph |
git log --oneline | jj log --no-graph | Compact log output |
git add | Not needed | jj auto-snapshots all changes |
git commit -m "..." | jj describe -m "..." then jj new | Describe current commit, start a new one |
git commit --amend | jj describe -m "..." | Message only — file changes are auto-snapshotted (no explicit amend needed for content) |
git branch <name> | jj bookmark set <name> | Create/move bookmark |
git branch -d <name> | jj bookmark delete <name> | Delete bookmark |
git branch -l | jj bookmark list | List bookmarks |
git checkout -b <name> | jj new + jj bookmark set <name> | New commit with bookmark |
git checkout <branch> | jj new <bookmark> | Creates a new empty commit on top of the branch tip; use jj edit <bookmark> to work directly on an existing commit (will rewrite history if pushed) |
git push | jj git push | Push bookmarks to remote |
git push -u origin <name> | jj git push --bookmark <name> | Push specific bookmark |
git fetch | jj git fetch | Fetch from remote |
git pull | jj git fetch then jj rebase -d main@origin | Fetch + rebase onto upstream |
git stash | jj new | Previous commit is preserved automatically |
git stash pop | jj new <previous-change-id> | Creates a new commit on top of the previous one (unlike git stash pop, this moves you rather than applying changes in place) |
git rebase -i (squash) | jj squash --from <rev> --into <rev> -m "msg" | Always pass -m to avoid interactive editor |
git worktree add <path> | jj workspace add <path> | Create parallel working copy |
git worktree remove | jj workspace forget <name> | Remove workspace |
git worktree list | jj workspace list | List workspaces |
Common Workflows
Making changes (the standard workflow)
jj status
jj log
jj describe -m "feat: add new feature"
jj new
Starting a new feature branch
jj git fetch
jj new main@origin
jj bookmark set feat/my-feature
jj describe -m "feat: implement my feature"
jj git push --bookmark feat/my-feature
Adding commits to an existing PR
jj new
jj describe -m "fix: address PR feedback"
jj bookmark set feat/my-feature
jj git push --bookmark feat/my-feature
Squashing commits
jj squash -m "feat: combined commit message"
jj squash --from <source-rev> --into <target-rev> -m "combined message"
jj squash --from <tip> --into <base> -m "final message"
IMPORTANT: Always pass -m to avoid opening an interactive editor, which fails in non-interactive terminals.
Syncing with upstream
jj git fetch
jj rebase -d main@origin
Parallel work with workspaces
jj workspace add ../workspace-name
cd ../workspace-name
jj new main@origin
jj bookmark set feat/other-feature
cd ../original-dir
jj workspace forget workspace-name
Viewing what changed
jj diff
jj diff -r @-
jj show <change-id>
jj diff --from <id1> --to <id2>
Key Differences from Git
- No staging area — all file changes are automatically tracked. There is no
git add.
- Commits are mutable —
jj describe changes the message, edits to files are auto-snapshotted.
- Working copy is always a commit —
@ always refers to a real commit, not uncommitted changes.
- Bookmarks are just pointers — they're like git branches but more lightweight. They must be explicitly set.
- Operations are logged — every jj operation is recorded. Use
jj op log to see history and jj op restore to undo.
- Git hooks are bypassed — jj commands do not trigger git-style hooks (pre-commit, pre-push, etc.), even in colocated repos. Quality checks should be handled by Claude Code hooks or CI instead.
When jj Is Not Available
If the .jj/ directory exists but jj is not found in PATH, do not fall back to git commands. This would corrupt jj's internal state. Instead, tell the developer:
"This repository is managed by jj, but jj is not found in PATH. Please install jj or add it to your PATH before performing version control operations."