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.mdfor the three-tier model andjj-version-control/SKILL.mdfor development-join mechanics - User explicitly requests workspace isolation: Read
jj-version-control/tiered-ceremony.md"Workspaces are not a tier" andjj-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] --helpFIRST - 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 newto freeze @ and create new empty @ on top - Git export: @ is jj-only until frozen; execute
jj newafterjj describefor 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 undoreverses any operationjj 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
# Atomic commit cycle (one file per change, matching git-preferences conventions)
# Edit ONE file, then immediately:
jj describe -m "message" # Describe current @ (ALWAYS use -m!)
jj new # Freeze @, start new empty @
# Repeat for next file. Without `jj new`, the next edit accumulates into the same change.
# If multiple files were edited before freezing, split after the fact:
jj split <path> -m "msg" # Extract one file into its own change
# Bookmarks
jj bookmark create <name> # Create at @
jj bookmark set <name> -r <c> # Move bookmark
# Move changes
jj squash # Move @ into parent
jj squash -r <commit> # Squash commit into parent
jj split <paths> -m "message" # Split @ by paths (REQUIRES -m!)
jj absorb # Auto-distribute @ to ancestors
# Rewrite history
jj rebase -r <c> -d <dest> # Move commit to new parent
jj abandon <commit> # Remove commit, rebase descendants
jj describe -r <c> -m "msg" # Reword commit (ALWAYS use -m!)
# Workspaces
jj workspace add <path> -r <c> # Create workspace
jj workspace update-stale # Update stale workspace
# Note: the `jj describe @` + `jj new` cycle above is for SINGLE-CHAIN (tier 1) work, where `@` becomes content.
# In a development join (tier 3), `@` is the shared empty [wip] directly atop the join — do NOT `jj describe @`
# into content and do NOT `jj rebase -r @` positionally; route DOWN via `jj squash --from @ ... --keep-emptied`,
# `jj absorb`, or `jj split` (keeping the wip), which leave `@` in place and empty.
# Multi-parent development join (composite working copy)
jj new bm-a bm-b bm-c # Join multiple chains into one working tree
jj describe -m "join: bm-a + bm-b + bm-c" # Describe the development join
jj new # Create wip commit on top of the join
# Work in wip, then route changes to the appropriate chain:
jj squash --into <chain> -u -- <path> # Manual routing (keeps dest description)
jj absorb # Auto-route changes by blame
# Route-and-extend: create a NEW commit on a chain (not amend existing); @/[wip] never drifts below the join
jj new -A <bookmark> --no-edit -m "feat: msg" # Insert empty commit after bookmark; --no-edit keeps @ on [wip]
jj squash --from @ --into <id> --keep-emptied -- <path> # Route file content into it; --keep-emptied preserves @ in place (NOT -u alone: -u only sets the dest description)
jj bookmark set <bookmark> -r <id> # Advance bookmark to new tip (use the printed Created-new-commit id, not @-)
# `@` stays an empty [wip] on the join. `jj describe -m ""` (empty message ONLY) may clear a stale wip description; never `jj describe @ -m "<content>"`.
# Route-and-extend multi-commit-range form: relocate an N-commit linear segment into a chain
jj rebase --revisions '<range-start>::<range-end>' --insert-after <chain-tip>
jj bookmark set <chain-bookmark> -r <range-end>
# See ~/.claude/skills/jj-version-control/SKILL.md §"Extending a chain with a new commit (route-and-extend pattern)" → Multi-commit-range form
# Add/remove chains dynamically:
jj rebase -r @ -d 'all:(@- | new-bm)' # Add chain to the development join
jj rebase -r @ -d 'all:(@- ~ old-bm)' # Remove chain from the development join
# Splice-below-join: insert a <base>-bound commit between <base> and chain roots
# By-construction: author a new splice commit in position
jj new --insert-before 'children(fork_point(parents(<join>))) & ::<join>' -m "msg"
# To route content currently in @/[wip] below the join, use the @-PRESERVING form (never moves @):
jj squash --from @ --insert-before 'children(fork_point(parents(<join>))) & ::<join>' \
-m "fix(scope): description" --keep-emptied -- <paths>
# By-relocation: ONLY for a SEPARATE, already-sealed NON-wip commit that already exists above the join.
# `<separate-sealed-non-wip-commit>` MUST NOT be `@`/[wip]; NEVER `jj describe @` then relocate it — that destroys the shared [wip].
# Precondition (by-relocation only): relocation set's files must be disjoint from any chain's files
# (skill/aggregator files frequently collide; on collision use route-and-extend or new-chain instead)
jj rebase --revisions <separate-sealed-non-wip-commit> --insert-before 'children(fork_point(parents(<join>))) & ::<join>'
# See ~/.claude/skills/jj-version-control/SKILL.md §"Splice-below-join"
# Diamond integration on remote advance: rebase diamond onto fast-forwarded remote
jj git fetch
jj rebase --source 'roots(<base>@origin..@)' --destination '<base>@origin'
jj bookmark set <base> -r '<base>@origin'
# See ~/.claude/skills/jj-version-control/SKILL.md §"Diamond integration on remote advance"
# Diamond-health diagnostic (surfaces all five diamond invariants in one view)
jj log -r 'present(@) | ancestors(immutable_heads().., 2) | trunk()'
# Recovery
jj undo # Undo last operation
jj op log # View operation history
jj op restore <id> # Restore to operation
# Git interop (colocated mode)
jj git init --colocate # Initialize in existing git repo
jj git fetch # Fetch from remote
jj git push --bookmark <name> # Push bookmark
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
# Work on changes (auto-snapshotted)
jj describe -m "[exp-1] feat: implement feature" # ALWAYS use -m
jj new # Freeze for git, start new @ on top
jj bookmark set exp-1 -r @- # Checkpoint (point to frozen commit)
jj git push --bookmark exp-1 # Share
Create workspace for serious work:
jj workspace add ../repo-exp-1 -r exp-1
cd ../repo-exp-1
# Work with persistent files
Clean up history incrementally:
jj log -r 'main..@' # Review
jj squash -r 'description(glob:"WIP*")' # Remove WIP
jj abandon 'empty()' # Remove empty
jj rebase -r <commit> -d <parent> # Reorder
jj describe -r <commit> -m "proper" # Reword
jj undo # Undo any mistake
Integrate experiment to main (linear history only, no merge commits):
# Option 1: Rebase single chain (preserve commits)
jj rebase -s 'main..exp-1' -d main
jj bookmark set main -r exp-1
# Option 2: Squash single chain (single commit)
jj new main
jj squash --from 'main..exp-1' --into @
jj bookmark set main -r @
# Option 3: Sequential linearization of multiple chains from a development join
# Rebase each chain onto main one at a time, advancing main after each.
# Order chains by dependency (independent chains first, dependents after).
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
# Repeat for each chain. Never create merge commits for integration to main.
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.
# Join multiple chains into one working tree
jj new feature-a feature-b feature-c
jj describe -m "join: feature-a + feature-b + feature-c"
jj new # Create wip commit on top of the join
# Work in wip, then route changes to the appropriate chain:
jj squash --into feature-a -u -- <path> # Manual routing (keeps dest description)
jj absorb # Auto-route by blame
# IMPORTANT: `jj squash --into` AMENDS the existing chain tip.
# To CREATE a new commit extending the chain (route-and-extend), `@`/[wip] stays the empty wip throughout —
# never `jj describe @` into content, never `jj rebase -r @`:
# jj new -A <bookmark> --no-edit -m "msg" # insert new empty commit after tip; --no-edit keeps @ on [wip]
# jj squash --from @ --into <new-id> --keep-emptied -- <path> # route content; --keep-emptied preserves empty [wip]
# jj bookmark set <bookmark> -r <new-id> # advance bookmark to new tip
# No description-recovery step is needed; [wip] is ephemeral.
# See jj-version-control/SKILL.md §"Extending a chain with a new commit (route-and-extend pattern)".
# Add/remove chains dynamically:
jj rebase -r @ -d 'all:(@- | new-bookmark)' # Add chain
jj rebase -r @ -d 'all:(@- ~ old-bookmark)' # Remove chain
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.
# Pre-edit recon: which chain (if any) already touched this file?
PAGER=cat jj log -r 'fork_point(@--)..@- & files("<path>")' \
--no-graph -T 'change_id.short() ++ " " ++ bookmarks ++ " " ++ description.first_line() ++ "\n"'
# Develop phase: N-way development join + wip (tactical example)
jj new chain-a chain-b chain-c
jj describe -m "join N=3: chain-a, chain-b, chain-c"
jj new # wip on top
jj squash --from @ --into <chain> -m "feat: desc" # route changes
Revset examples for experiments
# All experiments
jj log -r 'main..'
# Specific experiment
jj log -r 'main..exp-1@'
# Compare experiments (unique to exp-1)
jj log -r '(main..exp-1@) ~ (main..exp-2@)'
# Shared between experiments
jj log -r '(main..exp-1@) & (main..exp-2@)'
# All working-copy commits
jj log -r 'working_copies()'
# Your unbookmarked work
jj log -r 'mine() & ~bookmarks()'
Critical reminders
- Non-interactive execution: ALWAYS use
-m "message"withjj describe,jj describe -r, andjj split <paths>to avoid editor hangups; use-uor-mwithjj squash --intoto prevent the description merge editor; the development-join append-routejj squash --from @ --insert-after/--insert-beforeis create-a-new-commit mode and opens an editor without-m/-u, so always pass-mand--keep-emptiedso@stays an empty[wip]; verify unfamiliar commands withjj [subcommand] --helpfirst - Command verification protocol: Before executing any jj command you're uncertain about, run
jj [subcommand] --helpto check for interactive flags (look for-m, --message) - Git parity requirement: Execute
jj newimmediately afterjj describe -m "msg"to freeze commits for git export; withoutjj 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 newcycle should represent one logical change to one file — this is a standing directive matching the git-preferences atomic commit convention. Usejj splitto 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 logandjj op restoreinstead - 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. Neverjj describe @into a content commit and neverjj 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 pushedwipdeploy bookmark. Route DOWN into the owning chain viajj squash --from @ … --keep-emptied,jj absorb, orjj split(keeping the wip). Canon:~/.claude/skills/jj-version-control/SKILL.md§development join / invariants (iii-b)/(vi), anddiamond-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 viajj bookmark move main --to @-thenjj 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>thenjj 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 viajj squash --from @ --into <tip> -uplusjj bookmark move <name> --to <tip>, and rewrite the[merge]description in full whenever the parent set changes so it always reflects the currentjoin 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")
- Mid-diamond commit belongs on
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, andTaskdispatches withisolation: "worktree"are hook-blocked bygate-worktree-surfaces; parallel work uses the diamond development join, not worktrees. The explicit-workspace path isjj workspace addand 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.mdtier 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.mdfor mechanics andtiered-ceremony.mdfor 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