| name | jj-summary |
| description | Jujutsu workflow summary with essential paradigm shifts and decision guide. Reference for quick jj orientation. |
Jujutsu workflow summary
Quick reference and decision guide for jj version control. Full documentation: ~/.claude/skills/jj-workflow/SKILL.md
When to read full documentation
Read specific sections of jj-workflow.md when:
- First time with jj: Read "Core philosophy" and "Foundation" sections
- Starting parallel experiments: Read "Parallel experimentation with bookmarks"
- Multiple parallel work streams: Read
jj-version-control/tiered-ceremony.md for the three-tier model and jj-version-control/SKILL.md for development-join mechanics
- User explicitly requests workspace isolation: Read
jj-version-control/tiered-ceremony.md "Workspaces are not a tier" and jj-workflow/SKILL.md "Graduating to workspaces"
- Managing multiple experiments: Read "Experiment lifecycle management"
- Cleaning up history: Read "History refinement"
- Integration strategies: Read "Advanced patterns"
- Git interop questions: Read "Git colocated mode"
- Command lookup: Read "Reference" section
For quick questions about commands or concepts, this summary may suffice.
Essential paradigm shifts (git → jj)
Core differences:
- Working copy is always a commit (
@) that's automatically snapshotted
- Bookmarks don't move when you commit (only when commits are rewritten)
- No "current branch" - always in detached HEAD equivalent state
- Change IDs provide stable identity (commit IDs change, change IDs persist)
- Operation log is real history (commits are snapshots, operations are timeline)
- No staging area (working copy state is commit state)
- Conflicts are committed (resolved when convenient, never blocking)
Mental model shift:
- Git: "I'm working on branch X" → jj: "I'm building commit chain from X"
- Git: "Branch moves when I commit" → jj: "@ is rewritten when I change files"
- Git: "Switch branch to work elsewhere" → jj: "Create new @ anywhere"
- Git: "Branches are history" → jj: "Operation log is history"
Non-interactive execution for AI agents
Commands that launch editors by default (WILL HANG without proper flags):
| Command | Default behavior | Non-interactive pattern |
|---|
jj describe | Opens editor | jj describe -m "message" |
jj describe -r <c> | Opens editor | jj describe -r <c> -m "message" |
jj split <paths> | Opens editor twice (extracted + remainder) | jj describe -m "<remainder>" first, then jj split <paths> -m "<extracted>" — see jj-workflow "Common gotchas" for the multi-boundary rule |
jj split (no paths) | Opens diff editor (TUI) | Cannot be non-interactive |
jj squash --into <dest> | Opens description merge editor | jj squash --into <dest> -u (keep dest description) or -m "msg" |
jj squash --from @ --insert-after <tip> / --insert-before <revset> (create mode) | Opens editor for the new commit's description when neither -m nor -u is supplied | always pass -m "msg" (and --keep-emptied so @ returns to empty [wip]) |
Mandatory verification protocol:
- Not certain a command is non-interactive? → Run
jj [subcommand] --help FIRST
- Check help output for
-m, --message <MESSAGE> flag
- If command accepts
-m, ALWAYS use it to avoid editor launch
For untrusted call sites or in-flight hang recovery, see jj-workflow "Escape hatches for interactive operations" — covers the JJ_EDITOR=true belt-and-suspenders pattern and the SIGTERM + jj op restore recovery procedure.
Git parity requirement:
- jj working copy
@ exists only in .jj/ until frozen
- From git's perspective,
@ appears as uncommitted working directory changes
- Pattern:
jj describe -m "msg" → jj new → commit now visible in git
- Without
jj new, described commits remain invisible to git operations
Critical jj concepts
Working copy commit (@):
- Ephemeral commit constantly rewritten as you work
- Auto-snapshotted before each jj command
- Use
jj describe -m "message" to add description when cohesive (ALWAYS use -m)
- Use
jj new to freeze @ and create new empty @ on top
- Git export: @ is jj-only until frozen; execute
jj new after jj describe for git visibility
Bookmarks:
- Named pointers that stay put when you create commits
- Only move when commits are rewritten (rebase, squash, abandon)
- Update explicitly:
jj bookmark set <name> -r <commit>
- Multiple workspaces can work near same bookmark (no exclusive ownership)
Change IDs vs Commit IDs:
- Commit ID: changes with every rewrite (like git SHA)
- Change ID: stable across rewrites (tracks logical change)
- Use change IDs to track "same change" through rebase/amend
Operation log:
- Every jj operation recorded atomically
jj undo reverses any operation
jj op restore <id> returns to exact prior state
- Safety net replaces backup branches
Revsets:
- Query language for selecting commits (like SQL for commits)
- Examples:
main..@, mine() & ~bookmarks(), description(glob:"WIP*")
- Operate on multiple commits:
jj squash -r 'empty() & main..@'
Quick command reference
jj describe -m "message"
jj new
jj split <path> -m "msg"
jj bookmark create <name>
jj bookmark set <name> -r <c>
jj squash
jj squash -r <commit>
jj split <paths> -m "message"
jj absorb
jj rebase -r <c> -d <dest>
jj abandon <commit>
jj describe -r <c> -m "msg"
jj workspace add <path> -r <c>
jj workspace update-stale
jj new bm-a bm-b bm-c
jj describe -m "join: bm-a + bm-b + bm-c"
jj new
jj squash --into <chain> -u -- <path>
jj absorb
jj new -A <bookmark> --no-edit -m "feat: msg"
jj squash --from @ --into <id> --keep-emptied -- <path>
jj bookmark set <bookmark> -r <id>
jj rebase --revisions '<range-start>::<range-end>' --insert-after <chain-tip>
jj bookmark set <chain-bookmark> -r <range-end>
jj rebase -r @ -d 'all:(@- | new-bm)'
jj rebase -r @ -d 'all:(@- ~ old-bm)'
jj new --insert-before 'children(fork_point(parents(<join>))) & ::<join>' -m "msg"
jj squash --from @ --insert-before 'children(fork_point(parents(<join>))) & ::<join>' \
-m "fix(scope): description" --keep-emptied -- <paths>
jj rebase --revisions <separate-sealed-non-wip-commit> --insert-before 'children(fork_point(parents(<join>))) & ::<join>'
jj git fetch
jj rebase --source 'roots(<base>@origin..@)' --destination '<base>@origin'
jj bookmark set <base> -r '<base>@origin'
jj log -r 'present(@) | ancestors(immutable_heads().., 2) | trunk()'
jj undo
jj op log
jj op restore <id>
jj git init --colocate
jj git fetch
jj git push --bookmark <name>
Section index with triggers
Section: Core philosophy (lines 1-16 of jj-workflow.md)
- Read when: First time using jj, need paradigm explanation
- Covers: Fundamental differences from git, mental model shifts
- Key concepts: Auto-snapshotting, operation log, no special modes
Section: Automatic snapshotting preferences (lines 18-34)
- Read when: Configuring commit behavior, understanding preferences
- Covers: When to commit automatically, escape hatches
- Key concepts: Trust operation log, no staging area
Section: Foundation - Atomic commit workflow (lines 36-110)
- Read when: Learning basic jj workflow, single-workspace development
- Covers: Working copy commit behavior, bookmarks, operation log, conflicts
- Key concepts:
@ rewriting, bookmark management, undo/restore patterns
Section: Parallel experimentation with bookmarks (lines 112-176)
- Read when: Starting multiple experiments, comparing approaches
- Covers: Bookmark-based experiments, revset queries, checkpointing
- Key concepts: Single workspace with multiple bookmarks, exp-N naming
Section: Graduating to workspaces (lines 178-243)
- Read when: Need simultaneous file access, parallel builds/tests
- Covers: When to create workspaces, workspace lifecycle, stale handling
- Key concepts: workspace@, stale detection, cross-workspace operations
Section: Experiment lifecycle management (lines 245-372)
- Read when: Managing arbitrary N experiments, scaling patterns
- Covers: Naming conventions, revset aliases, registry, state transitions
- Key concepts: exp-N-description, docs/experiments.md, cleanup workflow
Section: History refinement (lines 374-557)
- Read when: Cleaning up commit history for review/PR
- Covers: Incremental cleanup, reorder/squash/split/reword/abandon
- Key concepts: No backup branches needed, jj undo at any step
Section: Advanced patterns (lines 559-643)
- Read when: Integrating experiments, handling dependencies
- Covers: Integration strategies (rebase/squash), sequential linearization, sub-experiments
- Key concepts: Stacking experiments, feature flags, session patterns
Section: Git colocated mode (lines 645-713)
- Read when: Working with existing git repos, git interop questions
- Covers: Initialization, remote sync, reverting to git-only
- Key concepts: .git and .jj coexist, automatic import/export
Section: Reference (lines 715-1045)
- Read when: Looking up revset syntax, command patterns
- Covers: Essential revset patterns, common commands, session summaries
- Key concepts: Comprehensive command reference, revset operators
Common workflow patterns (compressed)
Single-workspace experimentation:
jj bookmark create exp-1 -r main
jj new exp-1
jj describe -m "[exp-1] feat: implement feature"
jj new
jj bookmark set exp-1 -r @-
jj git push --bookmark exp-1
Create workspace for serious work:
jj workspace add ../repo-exp-1 -r exp-1
cd ../repo-exp-1
Clean up history incrementally:
jj log -r 'main..@'
jj squash -r 'description(glob:"WIP*")'
jj abandon 'empty()'
jj rebase -r <commit> -d <parent>
jj describe -r <commit> -m "proper"
jj undo
Integrate experiment to main (linear history only, no merge commits):
jj rebase -s 'main..exp-1' -d main
jj bookmark set main -r exp-1
jj new main
jj squash --from 'main..exp-1' --into @
jj bookmark set main -r @
jj rebase -s 'roots(main..feature-a)' -d main
jj bookmark set main -r feature-a
jj rebase -s 'roots(main..feature-b)' -d main
jj bookmark set main -r feature-b
Development join (simultaneous multi-bookmark editing):
In a development join, @ is the shared empty [wip] directly atop the join and the SHARED editing surface for all concurrent editors; do NOT jj describe @ into content and do NOT jj rebase -r @, route DOWN via jj squash --from @ ... --keep-emptied, jj absorb, or jj split (keeping the wip). The describe+new cycle taught earlier is for SINGLE-CHAIN (tier 1) work only.
jj new feature-a feature-b feature-c
jj describe -m "join: feature-a + feature-b + feature-c"
jj new
jj squash --into feature-a -u -- <path>
jj absorb
jj rebase -r @ -d 'all:(@- | new-bookmark)'
jj rebase -r @ -d 'all:(@- ~ old-bookmark)'
Diamond workflow (epic-scoped, four phases):
The diamond connects a beads epic graph to jj chain topology via diverge, develop, converge, serialize.
Canonical recipe: ~/.claude/skills/jj-version-control/diamond-workflow.md.
PAGER=cat jj log -r 'fork_point(@--)..@- & files("<path>")' \
--no-graph -T 'change_id.short() ++ " " ++ bookmarks ++ " " ++ description.first_line() ++ "\n"'
jj new chain-a chain-b chain-c
jj describe -m "join N=3: chain-a, chain-b, chain-c"
jj new
jj squash --from @ --into <chain> -m "feat: desc"
Revset examples for experiments
jj log -r 'main..'
jj log -r 'main..exp-1@'
jj log -r '(main..exp-1@) ~ (main..exp-2@)'
jj log -r '(main..exp-1@) & (main..exp-2@)'
jj log -r 'working_copies()'
jj log -r 'mine() & ~bookmarks()'
Critical reminders
- Non-interactive execution: ALWAYS use
-m "message" with jj describe, jj describe -r, and jj split <paths> to avoid editor hangups; use -u or -m with jj squash --into to prevent the description merge editor; the development-join append-route jj squash --from @ --insert-after/--insert-before is create-a-new-commit mode and opens an editor without -m/-u, so always pass -m and --keep-emptied so @ stays an empty [wip]; verify unfamiliar commands with jj [subcommand] --help first
- Command verification protocol: Before executing any jj command you're uncertain about, run
jj [subcommand] --help to check for interactive flags (look for -m, --message)
- Git parity requirement: Execute
jj new immediately after jj describe -m "msg" to freeze commits for git export; without jj new, described commits exist only in jj and appear as uncommitted changes in git
- Always colocated mode: We operate with both .git and .jj (can revert to git anytime)
- Operation log is safety net: Delete bookmarks freely, everything in operation log
- Start with bookmarks: Only create workspaces when need simultaneous file access
- Describe atomically: Each
jj describe + jj new cycle should represent one logical change to one file — this is a standing directive matching the git-preferences atomic commit convention. Use jj split to fix up if you forgot.
- Trust auto-snapshot: Changes saved before every command, recoverable via operation log
- Change IDs track identity: Commit IDs change on rewrite, change IDs don't
- No backup branches: Use
jj op log and jj op restore instead
- Bookmarks stay put: Explicitly move with
jj bookmark set, don't assume movement
- @ is ephemeral: Working copy commit constantly rewritten, bookmark @ parent not @
- @ stays empty
[wip] in a development join: when a multi-parent join exists, @ is the empty [wip] directly atop [merge] and the SHARED editing surface for all concurrent editors. Never jj describe @ into a content commit and never jj rebase -r @ / jj rebase --revisions @ — both drift @ off [wip], vanish the shared coordination point, break the join's one-child invariant (vi), and (in this repo) drag the pushed wip deploy bookmark. Route DOWN into the owning chain via jj squash --from @ … --keep-emptied, jj absorb, or jj split (keeping the wip). Canon: ~/.claude/skills/jj-version-control/SKILL.md §development join / invariants (iii-b)/(vi), and diamond-workflow.md.
- Conflicts are first-class: Committed and resolved when convenient, never blocking
- Detached HEAD is normal: In a jj-colocated repo, detached HEAD is the expected state. Do not attempt to reattach HEAD or "fix" this.
Quick decision tree
Which tier of ceremony does this work warrant? (See jj-version-control/tiered-ceremony.md for the full policy.)
-
Single-chain work with low verification severity (atomic local changes safe to land on the trunk, covered adequately by just check-fast)?
→ Tier 1: anonymous chain on @.
No bookmark beyond trunk, no PR; advance and push via jj bookmark move main --to @- then jj git push --bookmark main.
-
Need PR/CI validation via buildbot flake checks (multi-platform fleet matrix, large change benefiting from unified PR review, or fleet-wide gate before trunk)?
→ Tier 2: single named bookmark created retroactively on the chain.
jj bookmark create <name> -r <change-id> then jj git push --bookmark <name> --allow-new; follow ~/.claude/skills/nix-flake-pr-cycle/SKILL.md.
-
Multiple independent work streams in flight (multiple beads issues within an epic, parallel agent dispatch, parallel experiments to compose)?
→ Tier 3: diamond workflow via development join over the chains' bookmarks.
jj new <existing-bookmark> <new-bookmark> -m "join N=2: <alphabetical bookmarks, comma-separated>"; route edits via jj squash --from @ --into <tip> -u plus jj bookmark move <name> --to <tip>, and rewrite the [merge] description in full whenever the parent set changes so it always reflects the current join N=<cardinality>: <alphabetical bookmarks> state.
- Mid-diamond commit belongs on
<base> below all chains → splice-below-join (see ~/.claude/skills/jj-version-control/SKILL.md §"Splice-below-join")
- Remote
<base> advanced during diamond work → diamond integration on remote advance (see ~/.claude/skills/jj-version-control/SKILL.md §"Diamond integration on remote advance")
-
Did the user explicitly request workspace isolation (utterance naming worktree, workspace, isolate, separate working copy, or path forms like .worktrees/X)?
→ jj workspace add <path> -r <change> mechanics; otherwise stay at tier 3 and parallelize via the development join in a single working copy.
In jj mode, EnterWorktree, ExitWorktree, and Task dispatches with isolation: "worktree" are hook-blocked by gate-worktree-surfaces; parallel work uses the diamond development join, not worktrees.
The explicit-workspace path is jj workspace add and applies only when the user names workspace isolation specifically.
Should I read jj-workflow.md?
- Never used jj? → Read "Core philosophy" and "Foundation"
- Need parallel experiments? → Read "Parallel experimentation" and
jj-version-control/tiered-ceremony.md tier 3
- User explicitly requested workspace isolation? → Read "Graduating to workspaces"
- Working on 2+ independent chains? → Tier 3 development join over per-chain bookmarks; see
~/.claude/skills/jj-version-control/SKILL.md for mechanics and tiered-ceremony.md for the policy.
- Cleaning history? → Read "History refinement"
- Integrating work? → Read "Advanced patterns"
- Just need command? → Check "Quick command reference" above or "Reference" section
Should I use jj or git for this operation?
- History editing (rebase, squash, reorder)? → jj (more powerful, safer)
- Basic operations (commit, view log)? → jj (automatic snapshotting)
- Git-specific tools (e.g., GitHub CLI)? → git (works fine in colocated mode)
- Uncertain? → jj (can always undo with operation log)
For full documentation
- Full jj workflow reference:
~/.claude/skills/jj-workflow/SKILL.md
- Development join workflow, beads integration:
~/.claude/skills/jj-version-control/SKILL.md
- VCS mode detection, beads conventions:
~/.claude/skills/preferences-git-version-control/SKILL.md