jj-summary

star 13

Jujutsu workflow summary with essential paradigm shifts and decision guide. Reference for quick jj orientation.

cameronraysmith By cameronraysmith schedule Updated 6/7/2026

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:

  1. Not certain a command is non-interactive? → Run jj [subcommand] --help FIRST
  2. Check help output for -m, --message <MESSAGE> flag
  3. 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

# 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" 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.)

  1. 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.

  2. 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.

  3. 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")
  4. 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?

  1. Never used jj? → Read "Core philosophy" and "Foundation"
  2. Need parallel experiments? → Read "Parallel experimentation" and jj-version-control/tiered-ceremony.md tier 3
  3. User explicitly requested workspace isolation? → Read "Graduating to workspaces"
  4. 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.
  5. Cleaning history? → Read "History refinement"
  6. Integrating work? → Read "Advanced patterns"
  7. 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
Install via CLI
npx skills add https://github.com/cameronraysmith/vanixiets --skill jj-summary
Repository Details
star Stars 13
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
cameronraysmith
cameronraysmith Explore all skills →