Introducing jjw: a workspace manager for jj
I’ve just released jjw, a small command line tool for managing jj workspaces.
The short version is that jjw gives me a nicer workflow for creating, switching between, and deleting isolated jj workspaces. The slightly longer version is that I wanted a reliable way to run multiple coding agents in parallel without them trampling over each other’s working copies, local state, or setup.
It is inspired by wt, a Git worktree manager, but I decided to adapt it for working with jj and jj’s workspace and bookmark model.
Why I built it #
I’ve been using jj more recently and really like the model. It makes a lot of day-to-day version control operations feel calmer, especially once you get used to the working copy being a commit and the operation log being there as a safety net.
At the same time, my development workflow has changed. I often want to try a few ideas at once, or hand off different tasks to different LLM coding agents. That works best when each task has its own clean, isolated environment.
You can already do this with jj workspace add, but I found myself wanting a bit more structure around it:
- consistent workspace names and locations
- matching bookmarks for pull requests
- quick shell integration so creating or switching workspaces actually moves my shell there
- lifecycle hooks for setting up or tearing down per-workspace state
- safe deletion and cleanup once work has been merged
That became jjw.
What jjw does #
jjw manages workspaces inside a configured directory in your repository. A basic setup looks like this:
version: 1
workspace_dir: workspaces
default_branch: main
Once configured, creating a workspace is as simple as:
jjw create feature-x
Behind the scenes, jjw will:
- create a jj workspace at
workspaces/feature-x - create a bookmark for the workspace
- allocate a workspace index, if configured
- run any configured
post_createhooks - ask the shell integration to move you into the new workspace
From there you can work normally, push the bookmark with jj git push, open a pull request, and eventually clean things up when the work is merged.
The commands #
The core commands are intentionally small:
jjw create feature-x # create workspace + bookmark, then cd into it
jjw list # list workspaces
jjw cd feature-x # switch to a workspace
jjw exit # return to the main repo
jjw delete feature-x # delete workspace, bookmark, and files
jjw cleanup # remove clean workspaces with merged bookmarks
There are also configuration and utility commands:
jjw config init
jjw config get workspace_dir
jjw root
jjw version
The goal is not to wrap every part of jj. It is only there to smooth over the workspace lifecycle.
Hooks for real project setup #
The feature that makes this particularly useful for me is lifecycle hooks.
A lot of projects need a bit of per-workspace setup. Maybe you need a unique dev server port, a generated .env file, a temporary database name, or some other local resource. When running multiple agents in parallel, those details matter a lot more.
jjw supports hooks such as:
pre_createpost_createpre_deletepost_delete
Hooks receive useful environment variables, including:
JJW_NAME
JJW_PATH
JJW_BOOKMARK
JJW_REPO_ROOT
JJW_JJ_ROOT
JJW_WORKSPACE_DIR
JJW_INDEX
For example, you can use the allocated index to assign each workspace a different port:
hooks:
post_create:
- script: ./scripts/setup-ports.sh
#!/bin/bash
PORT=$((3000 + JJW_INDEX))
echo "Dev server port: $PORT"
That is the kind of small automation that makes parallel workspaces much easier to live with.
Getting started #
The easiest way to install jjw is with go install:
go install github.com/aranw/jjw@latest
Alternatively, you can build it from source. This requires Just to be installed.
git clone https://github.com/aranw/jjw.git
cd jjw
just install
Then add the shell integration for your shell:
# zsh
eval "$(jjw init zsh)"
# bash
eval "$(jjw init bash)"
# fish
jjw init fish | source
jjw currently expects jj 0.25+ and a colocated jj/git repository.
In a repository where you want to use it, initialise the config:
jjw config init
Then create your first workspace:
jjw create my-change
What’s next #
This is an initial release, so I expect the edges to become clearer as I use it more. The current focus is keeping the tool boring and predictable: create workspaces, connect them to bookmarks, run hooks, and clean them up safely.
If you use jj, run multiple local workstreams, or are experimenting with coding agents, I’d be interested to hear whether this fits your workflow.
The project is on GitHub here: github.com/aranw/jjw.