Skip to content

schpet/jjagent

Repository files navigation

jjagent

jjagent is a claude code plugin and program that uses hooks to associate a claude session change to it's own commit. this makes isolating the edits from you and other claude sessions easy.

this differs from having claude commit many times: there's a single, changing commit that you iterate on in a claude session. it's only split into another commit when a conflict would be detected or it is split by a user command.

it also differs from git worktree style systems: you and claude are working on the same working copy. this allows you to prevent conflicts, and for all the claude sessions to be in sync with each others changes. this may or may not be desirable, and you can use jjagent within jj workspaces to make separate branches of work.

the state is kept in the commit messages: a Claude-sessions-id trailer informs where jjagent will squash the claude session changes into, and also allows you to easily resume a claude session in the future.

how it works

when you start, @ is at the head. lets call @ 'users working copy' given its where you, the user works. you can change things while claude works away in the background and your changes will be here.

when a claude session is started and PreToolUse fires, jjagent will make a new change – a descendant of the users working copy. this is a fresh change for claude's changes to live in. after claude is done changing files, the PostToolUse fires and jjagent will squash those changes into a new direct ancestor of the users working copy. jj automatically rebases the descendants during the squash, and @ is back to the users working copy. subsequent claude edit tool calls will find the session's change based on a Claude-session-id trailer in the change description.

multiple claude sessions can be going at one, a lock file is used to have them wait their turn before editing files.

it's attribution is not perfect: you might write a file while we're on a claude change, and claude might use bash to change stuff. room for improvement here! but it works well for me.

assumptions, constraints, limitations

  • you need to keep @ as a descendent of claude's changes, the assumed workflow is that you will be working at the head or tip of descendants. if you move @ backwards while claude is doing its thing you are in for a bad time: claude will branch or otherwise do things on wrong assumptions
  • assumes you're running claude with 'accept edits on' or 'bypass permissions on'
  • when claude is editing files, avoid running jj commands that might have side effects. make sure to use --ignore-wroking-copy to prevent that
  • avoid running jj describe interactively: if claude code edits a file while you have your describe editor open you'll run into 'Error: The "@" expression resolved to more than one operation'
  • jjagent is currently only able to properly attribute changes from the Edit|MultiEdit|Write claude code tools, claude often changes files with bash and jjagent doesn't try to track that
  • right now, jjagent is coupled very tightly to claude code. hopefully other agents (codex cli, gemini cli, et al) support hooks similar to claude code in the future and can be supported.

installation

homebrew
brew install schpet/tap/jjagent
binaries

https://github.com/schpet/jjagent/releases/latest

from source
# clone jj agent locally
cargo install --path .

setup

via claude code plugin (recommended)

  1. add the marketplace and install the plugin:
    /plugin marketplace add schpet/jjagent
    /plugin install jjagent@jjagent
  2. restart claude code
  3. use claude code normally in a jj repo - jjagent runs automatically via hooks

Note

if you don't have the /plugin command you are likely running an old version of claude code. ensure you're on version 2.0.19 or later

via settings (fallback)

  1. update ~/.claude/settings.json with the json this command dumps out:
    jjagent claude settings
  2. use claude code normally in a jj repo - jjagent runs automatically via hooks

status line integration (optional, recommended)

jjagent can display your current session's jj change in claude code's status line. this gives you visibility into what commit your edits are being tracked in.

screenshot of a claude code session with a status line showing output like Sonnet 4.5 ✻ qxtqxkqq 602f8f0e Add feature

setup instructions
  1. create a status line script (e.g. ~/.claude/statusline.sh):

    #!/bin/bash
    input=$(cat)
    model=$(echo "$input" | jq -r '.model.display_name')
    jj_info=$(echo "$input" | jjagent claude statusline 2>/dev/null)
    printf "%s ✻%s" "$model" "${jj_info:+ $jj_info}"
  2. make it executable:

    chmod +x ~/.claude/statusline.sh
  3. configure it in ~/.claude/settings.json:

    {
      "statusline": {
        "command": "~/.claude/statusline.sh"
      }
    }
  4. restart claude code to see your status line:

    Sonnet 4.5 ✻ qxtqxkqq 602f8f0e Add feature
    

[!TIP] For more statusline customization options, see the Claude Code statusline docs

plugin features

the jjagent plugin provides additional commands and agents for working with session changes:

prerequisites: you must have jjagent installed (see installation) before installing the plugin. the plugin just sets up hooks and provides convenience commands - the actual jjagent binary does the heavy lifting.

slash commands

  • /jjagent:describe "example message... - describe this session's change with a provided message
  • /jjagent:describe - generate a message based on the diff
  • /jjagent:insert-after - create a new change after a specific ref where this session's edits will live, if you already have a change for this session it'll help rebase it after this ref
  • /jjagent:into - mark an existing change to squash this session's edits into
  • /jjagent:split - split future edits into a new change

hooks

fyi: these are setup by the plugin automatically

the plugin automatically configures these hooks:

  • SessionStart - injects session ID into claude's context at the start of each session to support slash commands
  • UserPromptSubmit - re-injects session ID if it's been lost from recent context (i.e. compact)
  • PreToolUse / PostToolUse - manages session changes around file edits (edit, write tools)
  • Stop - cleanup when claude session ends

mood board

You see, jj was designed around a single feature requirement. That requirement led to a very simple design addition to Git's DVCS model, that naturally enabled all of the features:

jj was designed to support concurrency.

Jujutsu is great for the wrong reason

acknowledgements

inspired directly by gitbutler's claude code hooks

About

track claude code sessions as jj-vcs changes

Topics

Resources

License

Stars

Watchers

Forks