name: jujutsu
description: “REQUIRED - Always activate FIRST on any git/VCS operations (commit, status, branch, push, etc.), especially when HEAD is detached. If .jj/ exists -> this is a Jujutsu (jj) repo - raw git commands can corrupt data. Essential git safety instructions inside. DO NOT IGNORE.”
allowed-tools: Bash(jj *)
Jujutsu (jj) Version Control System
This skill helps you work with Jujutsu, a Git-compatible VCS with mutable commits and automatic rebasing.
Tested with jj v0.37.0 - Commands may differ in other versions.
Important: Automated/Agent Environment
When running as an agent:
- Always use
--no-pagerto prevent commands from opening an interactive pager (likeless), which will hang the agent:
# Always use --no-pager on commands that produce output
jj --no-pager log # NOT: jj log
jj --no-pager diff # NOT: jj diff
jj --no-pager show <id> # NOT: jj show <id>
- Always use
-mflags to provide messages inline rather than relying on editor prompts:
# Always use -m to avoid editor prompts
jj desc -m "message" # NOT: jj desc
jj squash -m "message" # NOT: jj squash (which opens editor)
Editor-based commands will fail in non-interactive environments.
- Verify operations with
jj stafter mutations (squash,abandon,rebase,restore) to confirm the operation succeeded.
Core Concepts
The Working Copy is a Commit
In jj, your working directory is always a commit (referenced as @). Changes are automatically snapshotted when you run any jj command. There is no staging area.
There is no need to run jj commit.
Commits Are Mutable
CRITICAL: Unlike git, jj commits can be freely modified after creation. You can update descriptions, squash changes, rebase, and absorb — all without creating new commits. See “Essential Workflow” below for the recommended working pattern.
Change IDs vs Commit IDs
- Change ID: A stable identifier (like
tqpwlqmp) that persists when a commit is rewritten — prefer these when referencing commits - Commit ID: A content hash (like
3ccf7581) that changes when commit content changes
Revsets
jj uses a revset language to select commits in commands. Common revsets:
@— the working copy commit@-— the parent of the working copy::@— all ancestors of@@::— all descendants of@trunk()..@— commits between trunk and@(your branch)bookmarks()— all commits with bookmarks
Use revsets with -r flags: jj log -r 'trunk()..@'
Essential Workflow
Starting Work: Describe First, Then Code
Always create your commit message before writing code:
Validate that you’re on a blank revision with jj st. If you are not, you should type:
jj new
# First, describe what you intend to do
jj desc -m "Add user authentication to login endpoint"
# Then make your changes - they automatically become part of this commit
# ... edit files ...
# Check status
jj st
Creating Atomic Commits
Each commit should represent ONE logical change. Use this format for commit messages:
Examples:
- "Add validation to user input forms"
- "Fix null pointer in payment processor"
- "Remove deprecated API endpoints"
- "Update dependencies to latest versions"
Viewing History
# View recent commits
jj --no-pager log
# View with patches
jj --no-pager log -p
# View specific commit
jj --no-pager show <change-id>
# View diff of working copy (use --git for familiar +/- format)
jj --no-pager diff --git
IMPORTANT: jj diff output format: The default jj diff output uses a side-by-side line number format (e.g. 26 26:) that looks very different from git’s +/- prefix format. This is normal and correct — it is NOT corrupted or showing stale content. However, to avoid confusion, always use jj diff --git to get standard unified diff format with +/- lines.
Moving Between Commits
# Create a new empty commit on top of current
jj new
# Create new commit with message
jj new -m "Commit message"
# Edit an existing commit (working copy becomes that commit)
jj edit <change-id>
# Edit the previous commit
jj prev -e
# Edit the next commit
jj next -e
Refining Commits
Squashing Changes
Move changes from current commit into its parent:
# Squash all changes into parent
jj squash
Note: jj squash -i opens an interactive UI and will hang in agent environments. Avoid it.
Splitting Commits
Warning: jj split is interactive and will hang in agent environments. To divide a commit, use jj restore to move changes out, then create separate commits manually.
Absorbing Changes
Automatically distribute changes to the commits that last modified those lines:
# Absorb working copy changes into appropriate ancestor commits
jj absorb
Abandoning Commits
Remove a commit entirely (descendants are rebased to its parent):
jj abandon <change-id>
Undoing Operations
Reverse the last jj operation:
jj undo
This reverts the repository to its state before the previous command. Useful for recovering from mistakes like accidental abandon, squash, or rebase.
Rebasing Commits
Move commits to a different parent:
# Rebase current branch onto a destination
jj rebase -d <destination>
# Rebase a specific revision (without descendants) onto a destination
jj rebase -r <change-id> -d <destination>
# Rebase a revision and all its descendants
jj rebase -s <change-id> -d <destination>
# Rebase onto trunk (common: update your branch to latest main)
jj rebase -d main
Restoring Files
Discard changes to specific files or restore files from another revision:
# Discard all uncommitted changes in working copy (restore from parent)
jj restore
# Discard changes to specific files
jj restore path/to/file.txt
# Restore files from a specific revision
jj restore --from <change-id> path/to/file.txt
Working with Bookmarks (Branches)
Bookmarks are jj’s equivalent to git branches:
# Create a bookmark at current commit
jj bookmark create my-feature -r@
# Move bookmark to a different commit
jj bookmark move my-feature --to <change-id>
# List bookmarks
jj --no-pager bookmark list
# Delete a bookmark
jj bookmark delete my-feature
Workspaces
A workspace is a working copy plus its associated repo. One repo can have multiple workspaces — each with its own working directory and working-copy commit (@) — all sharing the same commits, operations, and bookmarks. This is jj’s equivalent of git worktree.
Useful for running a long build or test in one workspace while editing in another. Workspaces are a rarely-needed feature; consult the official docs for anything beyond the basics below.
Common commands
# Create a new workspace (defaults: name = basename of path, parent = current @'s parent)
jj workspace add ../my-tests
jj workspace add --name tests -r <change-id> ../my-tests # explicit name and base
# Inspect
jj --no-pager workspace list
jj workspace root [--name <ws>]
# Remove (does NOT delete files on disk — rm the directory separately)
jj workspace forget [<ws>]
# Rename current workspace
jj workspace rename <new-name>
In jj log, each workspace’s @ appears as <workspace-name>@.
Key semantics
- Isolation by default.
jj workspace addgives the new workspace its own fresh empty commit; workspaces don’t start out sharing@, and on-disk files are never live-mirrored between them. - Propagation at command boundaries. Each jj command snapshots the current workspace’s files and reads the op log, so it sees commits/bookmarks made by other workspaces. There is no filesystem watcher.
- Stale working copy. If another workspace rewrites this workspace’s
@(e.g. viajj squash,rebase,abandon), jj refuses commands here until you runjj workspace update-stale. Same recovery path if a command was interrupted mid-update. - Shared
@is sharp-edged.jj edit <id>lets two workspaces point at the same change without warning. When one mutates it, the other goes stale; if the stale one had un-snapshotted edits,update-stalepreserves them as a divergent commit (same change ID, shown asxyz??injj log) that you must resolve. Avoid sharing@unless both workspaces are read-only.
Agent guidance
- Always pass
--no-pagertojj workspace list. - Don’t
jj edita change another workspace already has as its@— main cause of accidental divergence. - Don’t
rm -rfa workspace directory without also runningjj workspace forget <name>.
Git Integration
Working with Existing Git Repos
# Clone a git repository
jj git clone <url>
# Initialize jj in an existing git repo
jj git init --colocate
Fetching Remote Changes
# Fetch all branches from the default remote
jj git fetch
# Fetch from a specific remote
jj git fetch --remote <remote-name>
# Fetch specific branches
jj git fetch -b <branch-name>
After fetching, rebase your work onto the updated trunk: jj rebase -d main
Switching Between jj and git (Colocated Repos Only)
This section only applies to colocated repos (where both .jj/ and .git/ exist). In non-colocated repos, do not use git commands — they will corrupt jj state.
In a colocated repository, you can use both jj and git commands with care:
Switching to git mode (e.g., for merge workflows):
# First, ensure your jj working copy is clean
jj st
# Then checkout a branch with git
git checkout <branch-name>
Switching back to jj mode:
# Use jj edit to resume working with jj
jj edit <change-id>
Important notes:
- Git may complain about uncommitted changes if jj’s working copy differs from the git HEAD
- ALWAYS ensure your work is committed in jj before switching to git
- After git operations, jj will detect and incorporate the changes on next command
Pushing Changes
When the user asks you to push changes:
# Push a specific bookmark to the remote
jj git push -b <bookmark-name>
# Example: push the main bookmark
jj git push -b main
Before pushing, ensure:
- Your bookmark points to the correct commit (bookmarks don’t auto-advance like git branches)
- The commits are refined and atomic
- The user has explicitly requested the push
IMPORTANT: Unlike git branches, jj bookmarks do not automatically move when you create new commits. You must manually update them before pushing:
# Move an existing bookmark to the current commit
jj bookmark move my-feature --to @
# Then push it
jj git push -b my-feature
If no bookmark exists for your changes, create one first:
# Create a bookmark at the current commit
jj bookmark create my-feature
# Then push it
jj git push -b my-feature
Handling Conflicts
jj allows committing conflicts — you can resolve them later:
# View conflicts
jj st
Agent conflict resolution: Do not use jj resolve (interactive). Instead, edit the conflicted files directly to remove conflict markers, then run jj st to verify resolution.
Preserving Commit Quality
IMPORTANT: Because commits are mutable, always refine them before considering work done:
- Review your commit:
jj --no-pager show @orjj --no-pager diff --git - Is it atomic? One logical change per commit
- Is the message clear? Use imperative verb phrase in sentence case format with no full stop: e.g. “Add login endpoint”, “Fix null pointer in payment processor”, “Remove deprecated API endpoints”
- Are there unrelated changes? Use
jj restoreto move changes out, then create separate commits - Should changes be elsewhere? Use
jj squashorjj absorb
AI attribution
Detect before writing any commit.
- Model: stated in system prompt as “You are powered by the model named X” — use X. Never hardcode.
- Agent: identity declared in system prompt (e.g. “Your designated identity is Build”) — use that. Never use “Claude”.
- CLI: check env vars —
OPENCODE=1→opencode;CLAUDECODEset →claude; neither → omit CLI part.
Commits
Conventional Commits format by default, prefer to use project’s commit style. Subject ≤50 chars. Body only when “why” isn’t obvious.
Always append trailer:
Assisted-by: <agent>:<model> <cli>
Example:
feat(hooks): add per_dir_hook support for lorem_ipsum
Assisted-by: build:deepseek-v4-pro opencode
Quick Reference
| Action | Command |
|---|---|
| Describe commit | jj desc -m "message" |
| View status | jj st |
| View log | jj --no-pager log |
| View diff | jj --no-pager diff --git |
| New commit | jj new -m "message" (use jj st first; skip if @ is empty) |
| Edit commit | jj edit <id> |
| Squash to parent | jj squash |
| Auto-distribute | jj absorb |
| Rebase | jj rebase -d <destination> |
| Abandon commit | jj abandon <id> |
| Undo last operation | jj undo |
| Restore files | jj restore [paths] |
| Create bookmark | jj bookmark create <name> |
| Fetch remote | jj git fetch |
| Push bookmark | jj git push -b <name> |
| Add workspace | jj workspace add <path> |
| List workspaces | jj --no-pager workspace list |
| Forget workspace | jj workspace forget [name] |
| Fix stale working copy | jj workspace update-stale |
Best Practices Summary
- Describe first: Set the commit message before coding
- One change per commit: Keep commits atomic and focused
- Use change IDs: They’re stable across rewrites
- Refine commits: Leverage mutability for clean history
- Embrace the workflow: No staging area, no stashing - just commit
- Use Conventional Commits: Follow conventional commits format, subject ≤50 chars
- Always attribute: Append
Assisted-by: <agent>:<model> <cli>trailer on every commit