repolane v0.11.0 · open source

Guides

Install

What Repolane needs, how to install it, how to make a workspace, how to check it worked, how to upgrade, and how to take it off again.

On this page

In brief. Repolane is a Python package: pipx install repolane (or uv tool install repolane), once per machine. Then make a workspace, any folder where you run lane init. You need git, python3 (3.9 or later) and bash; gh and Claude Code are optional. Nothing is compiled and your shell profile is never edited. lane doctor checks it worked, lane upgrade keeps it current, and there is no uninstall command (see Taking it off).

pipx install repolane                 # or: uv tool install repolane. Puts `lane` on your PATH
mkdir ~/work && cd ~/work
lane init                             # makes this folder a workspace; asks before touching CLAUDE.md or ~/.claude
lane add https://github.com/octocat/Hello-World.git hello   # a public demo repo, so this runs as written
lane start TRY-1 hello                                       # a branch and worktree of it, as lane TRY-1
cd lanes/TRY-1 && claude

octocat/Hello-World is GitHub’s public sample repository (default branch master); it is there so every line above works exactly as written. For your own work, replace it with your repo’s URL, or with a path to a checkout you already have (lane add ~/code/api api), and repeat lane add for each repo a lane will touch. A lane can only name repos that have been added.

  Make these links under ~/.claude? [Y/n]
  made  memory of the api mirror linked: ~/.claude/projects/-Users-john-mercer-work--repolan
e-repos-api/memory -> .repolane/memory/api
~/work $ lane add ~/code/web.git web
  do you already have this cloned somewhere? path (blank to skip):
cloned /Users/john.mercer/code/web.git -> /Users/john.mercer/work/.repolane/repos/web
registered web (default main); its memory is .repolane/memory/web. Edit .repolane/registry/r
epos.yaml: stack/product.

  Claude Code keeps memory under ~/.claude/projects. Repolane links it into the workspace, s
o it lives here:

      ~/.claude/projects/-Users-john-mercer-work--repolane-repos-web/memory -> .repolane/mem
ory/web   (the web mirror; new link)

  Make these links under ~/.claude? [Y/n]
  made  memory of the web mirror linked: ~/.claude/projects/-Users-john-mercer-work--repolan
e-repos-web/memory -> .repolane/memory/web
~/work $ lane status
guard: 11/11 probes answered as they should, no lanes yet — nothing to wire but the root (1/
  —  lane doctor for the evidence
== workspace (its own git repo, if you made one: docs/install.md)
  not a git repo — memory/, knowledge/, CLAUDE.md are not tracked anywhere
== repos (primary mirrors)
  api: main, behind origin by 0, worktrees=0
  web: main, behind origin by 0, worktrees=0
== lanes
== drift: checkouts outside the workspace (should be none; work happens here)
~/work $ exit

The last screen of a real asciinema recording, as text. With JavaScript the player replays the whole session from the start.

Run for real: pipx install repolane, then lane init in a new folder (it asks for your name and before linking Claude Code's memory), lane add for two repos, and lane status. The owner, paths (/Users/john.mercer) and repo names are made up; the output is otherwise unedited. Recorded 2026-10-22. Message wording can differ from what you see today; run it to see the current text.

Use with care. Claude can make mistakes, and the hooks are a guardrail, not a sandbox. Review what it changes; you are responsible for what runs on your machine. Details.

Already using Repolane from a git clone? See Moving from a git-clone install.

Requirements

gitrequired. Worktrees are the whole mechanism, and the code calls git branch --show-current (git 2.22) and, in the board, git restore --staged (2.23). Use 2.23 or newer
python3required, 3.9 or later. The package, the hook dispatcher and the board are standard-library Python, with no dependencies
bashrequired. Every command is a bash script. The stock macOS bash 3.2 works
pipx or uvto install the package. Plain pip into a virtual environment works too
OSmacOS (bash 3.2) and Linux. The test suites pass on macOS and in an Ubuntu 24.04 container; live Claude Code sessions were run on macOS only. The repository’s CI workflow defines an Ubuntu job and a macOS job pinned to /bin/bash 3.2. Windows: not tried (the README suggests WSL)
ghoptional. Without it you lose pull request status, lane gh and lane sync
Claude Codeoptional. The commands work on their own; the hooks only matter when Claude is driving

What has and has not been checked. lane init only checks that git exists; it does not check versions, so the minimums above come from reading what the code calls, not from a check the tool performs. The steps on this page were run on macOS with git 2.39 and Python 3.13. The README says to use WSL on Windows; we have not tried it.

Install the package

pipx install repolane       # or: uv tool install repolane

Do this once per machine. It puts lane on your PATH, plus the long command names next to it (lane-start, lane-run, lane-memory, …, and guard-check), 28 commands in all. Each long name is the same as lane <command>. They are what the guard messages and the rules tell a Claude session to type, and they run from any folder.

The package holds Repolane’s own files: the commands, the guard, the rule files it writes into a workspace. Your repos, lanes, memory and notes are never part of it, so upgrading or reinstalling it never touches them.

pipx and uv put the commands in ~/.local/bin. If that folder is not on your PATH, they say so (pipx ensurepath adds it). The hooks call lane by name, so it has to be on the PATH Claude Code sees; lane doctor checks that.

Check that it worked

lane --version

prints lane X.Y.Z. Then, inside a workspace:

lane doctor
$ lane doctor
Workspace ~/work:
  ok    `lane` on PATH runs this workspace's hooks (~/.local/bin/lane, version 0.10.0)
  ok    CLAUDE.md imports .repolane/REPOLANE.md
  ok    .claude/settings.json runs Repolane's lane hook commands
  ok    .repolane/REPOLANE.md and LANE.md are version 0.10.0's
  ok    registered in ~/.local/share/lane/planes.json
  ok    every lane's settings.local.json runs the current hooks (13 file(s))
  ok    Claude Code's memory is linked into .repolane/memory (6 folder(s) under ~/.claude/projects)
  ok    Repolane's slash commands and agents are in .claude/ (11 file(s), version 0.10.0's)
  ok    status line: lane statusline

guard.py compiles; 11/11 probes answered as they should:
  deny   a worktree with no lane named
  deny   a worktree opened by path
  deny   reading a .env
  deny   a shell that prints a .env
  deny   writing a memory directly
  deny   a push to a default branch
  deny   rewriting the git remote
  deny   a push to main, written differently
  deny   grepping a .env (Grep reaches it)
  deny   deleting a GitHub repo
  pass   ordinary work is untouched
14/14 places point at guard.py: 4 lane(s), 9 worktree(s), the root

gh is not authenticated — some commands (lane sync, lane gh) will not work until: gh auth login
hundreds more cases behind this (the whole suite):  lane doctor -v

Recorded from a real run. Probe list and wording can differ from what you see today; run lane doctor to see the current output.

lane doctor first checks the workspace: that lane on your PATH is the one the hooks will run, that CLAUDE.md imports Repolane’s rules, that .claude/settings.json and every lane run the expected lane hook commands, that the rule files match the installed version, that the workspace is registered, that Claude Code’s memory is linked, that Repolane’s slash commands and agents in .claude/ are the installed version’s, and the status line. Then it fires a short list of named probes at the guard (all but one must be refused; one ordinary command must not be), counts the places that point at the guard, and points at the further guard cases. Each problem comes with its fix, and lane doctor --fix repairs what it safely can, asking before it edits CLAUDE.md or anything under ~/.claude. lane doctor -v also lists every location and runs the full guard test suite; lane doctor -q prints one line. The exit code is non-zero if any of it is wrong. If gh is not signed in you get the advisory line shown above; it is not a failure.

Make a workspace: lane init

mkdir ~/work && cd ~/work
lane init                    # or: lane init ~/work

Any folder where lane init ran is a workspace. lane finds it by looking up from the current folder, the way git finds a repo, so every command works from anywhere inside it. LANE_WORKSPACE=<folder> names one directly. A workspace cannot sit inside another one.

$ lane init --yes
Setting up the workspace ~/work for John Doe:
  made  .repolane/registry/lanes/
  made  .repolane/memory/
  made  .repolane/knowledge/
  made  .repolane/repos/
  made  .repolane/cache/
  made  lanes/
  made  .repolane/config.yml
  made  .repolane/registry/repos.yaml
  made  .repolane/memory/MEMORY.md
  made  .repolane/knowledge/preferences.md
  made  .repolane/env.sh
  made  CLAUDE.md (it imports .repolane/REPOLANE.md)
  made  .claude/settings.json (Repolane's hooks)
  made  .claude/settings.json (status line: lane statusline)
  made  .claude/commands, .claude/agents (Repolane's 11 slash command and agent file(s))
  made  .repolane/REPOLANE.md, .repolane/LANE.md (version 0.10.0)

  Claude Code keeps memory under ~/.claude/projects. Repolane links it into the workspace, so it lives here:

      ~/.claude/projects/-Users-john-mercer-work/memory -> .repolane/memory   (the workspace root; new link)

  made  memory of the workspace root linked: ~/.claude/projects/-Users-john-mercer-work/memory -> .repolane/memory
  made  registered in ~/.local/share/lane/planes.json (lane upgrade refreshes every registered workspace)

Next: bring a repo in, then start a piece of work:

  lane add https://github.com/<org>/<repo>.git
  lane start <id> <repo>
  claude

note: Claude can make mistakes. The hooks are a guardrail, not a sandbox: review what Claude changes.

Run in a terminal, it asks up to three things:

QuestionWhenNon-interactive form
Your name, used when the hooks address youalways, unless .repolane/config.yml already names you--owner "Sam" (with --yes, the default is git config user.name)
Add the line @.repolane/REPOLANE.md to your CLAUDE.md?only when the folder already has a CLAUDE.md. A folder without one gets a short one with the line in it--yes adds it
Make the memory links under ~/.claude?when Claude Code’s memory for this folder is not linked yet--yes makes them

The memory links: Claude Code keeps a project’s memory in ~/.claude/projects/<folder>/memory. Repolane links that folder to .repolane/memory, so memory lives in the workspace. Notes already in Claude Code’s folder are copied in first. Nothing under ~/.claude changes without a yes, and your CLAUDE.md is never edited without one.

With no terminal and no --yes, lane init changes neither your CLAUDE.md nor anything under ~/.claude, and prints what to run instead. A link you said no to is recorded in .repolane/declined.json and not offered again; lane doctor shows it as a note, and lane doctor --fix offers it once more.

Other flags: --git makes the workspace a git repo of your own (see below), and --claude-home <dir> names Claude Code’s folder if it is not ~/.claude (or set CLAUDE_HOME).

Two things init does without asking, both easy to undo:

  • It adds Repolane’s hooks to .claude/settings.json, creating the file if there is none. Your own keys and hooks in an existing file are kept. When the file has no status line it sets one, lane statusline; a status line of your own is never replaced.
  • It copies Repolane’s slash commands and agents into .claude/commands/ and .claude/agents/, next to any of your own. A file of yours with the same name is kept and reported.
  • It registers the workspace in ~/.local/share/lane/planes.json, which is how lane upgrade finds every workspace on the machine.

lane init is safe to run again. Over a workspace in use it changes nothing and says so.

The ticket tracker link, the stale-lane reminder and the lane sync reminder are not questions: they are settings in .repolane/config.yml, which is yours to edit (tracker: url:, lanes: stale_days:, protection: sync_days:).

What is in a workspace

Three things at the top are yours and Claude Code’s; everything Repolane keeps is under .repolane/.

~/work/
  CLAUDE.md                  yours. It imports Repolane's rules with the line  @.repolane/REPOLANE.md
  .claude/settings.json      Claude Code's settings: Repolane's `lane hook …` commands, `lane statusline`,
                             and anything of your own
  .claude/commands/          Repolane's slash commands (/status, /lane-brief, /lane-start, …), beside your own
  .claude/agents/            Repolane's agents (lane-manager, context-loader, branch-gardener), beside your own
  lanes/<id>/<repo>/         the worktrees: one folder per piece of work
  .repolane/
    REPOLANE.md              the rules every session here gets (through the import line in CLAUDE.md)
    LANE.md                  the rules a session inside a lane gets, handed to it as it enters
    version                  which Repolane version wrote REPOLANE.md and LANE.md
    config.yml               your name, the ticket tracker URL, stale-lane and sync reminders
    env.sh                   shared build caches and environment settings, sourced before a repo's build
    registry/repos.yaml      the repos, where they came from, default branch, stack
    registry/lanes/<id>.md   one file per lane: goal, resume note, findings, log, the repos in scope
    registry/rules.yaml      rules of your own (optional; see Rules)
    memory/                  cross-repo memory; memory/<repo>/ per repo (Claude Code's memory links point here)
    knowledge/               preferences.md and your notes; knowledge/shared/ is what `lane rules pull` brings
    repos/<repo>/            the base clones: one clone of each repo, always on its default branch, never edited
    cache/                   machine-local caches: session state, the memory inbox, logs
    scripts/local/           your own `lane <name>` commands (optional; see Extending)
    declined.json            the memory links you said no to (only when there are some)
    claude-files.json        which slash command and agent files Repolane wrote into .claude/, so it never replaces yours

REPOLANE.md, LANE.md and version are Repolane’s: they are written again on the first lane command or hook after the installed version changes, so do not edit them. Nothing else in the workspace is ever overwritten. Neither rule file carries your name; the hooks name you when a session starts.

Repolane’s slash commands and agents are copied into .claude/commands/ and .claude/agents/ by name, and brought up to date with the rule files. Only a file that is still exactly what Repolane wrote is ever replaced: a file of your own with the same name, or one of Repolane’s you edited, is kept (lane init and lane doctor say so), and one you delete stays deleted. See Working with Claude.

The hooks in .claude/settings.json and in every lane’s settings are commands (lane hook pre-tool-use, lane hook session-start, …), not paths into the installed package. That is why lane has to be on PATH for the hooks to work (see Troubleshooting).

repos/ and lanes/ are machine state, described by registry/. Read Where state lives for what each folder holds and what survives a resume or a lane done.

Bringing repos in

lane add https://github.com/octocat/Hello-World.git hello   # a public demo repo; swap in your own URL
lane add ~/code/api                 # an existing checkout works too (replace with your own path)
lane add ~/code/api service-api     # give it a different name

Each one is cloned or copied into .repolane/repos/<name> and registered in .repolane/registry/repos.yaml. The base clone stays on its default branch: nothing is ever edited there, and every lane’s worktrees share its object store. It is a normal working clone, not a bare one.

In a terminal, lane add <url> first asks do you already have this cloned somewhere? path (blank to skip). That answer is only a record, the same as lane add <url> --local <path>: it still clones from the URL, but it registers the path as an old checkout (see below) so conversations you had there are recognised. To copy a checkout’s git database instead of cloning, use lane add <url> --from <path>, or give the path as the first argument. Then it asks before linking the new base clone’s Claude Code memory to .repolane/memory/<name> (--yes accepts). With no terminal it asks nothing and changes nothing under ~/.claude.

lane add also scans the repo for filenames that look like secrets (id_rsa, .npmrc, service-account.json, …) and, in a terminal, offers to deny them to Claude sessions. Most candidates turn out to be ordinary source, so it asks rather than assuming. With no terminal it only lists them. See Rules.

Adopting a checkout you already have

lane add ~/code/api is how most people start: not a fresh clone, but a checkout that already exists, with branches and maybe stashes in it. Precisely:

  • It copies; the original is not modified. The .git directory is copied into .repolane/repos/<name>. Nothing in your checkout is written, moved or deleted.
  • Every local branch comes across, because the whole git database does. Stashes too.
  • The default branch of the copy is reset to origin/<default>. After copying, if the copy has an origin/<default> ref, it runs git reset --hard origin/<default> on its default branch. Commits you had on that branch in the original that are not pushed are therefore not on that branch in the copy. The commits are still in the copy’s object store and reflog, and still in your original, but if you meant to carry unpushed work on main across, push it first. (A checkout with no origin has no such ref, so nothing is reset.)
  • Worktrees of the original are dropped. Their metadata is not copied. Recreate the work as a lane (lane start <id> <repo>:<branch>); the branch itself came across.
  • Ignored files are copied if they look useful. Files matching .env*, settings.local.json and *.code-workspace that git ignores in your checkout are copied into the base clone, and lane start then links env files into every lane as symlinks. That means a copy of your .env exists under .repolane/repos/<name>/. The guard refuses sessions access to it, but it is a second copy.
  • The original is recorded as an “old checkout”. lane add writes its path to .repolane/registry/old-checkouts.json. From then on lane status flags work happening there, and the guard refuses sessions any read, edit or shell mention of that path (“frozen copy from before the migration”). That is deliberate, so nobody edits the stale copy by mistake, and it also means a session cannot read your original checkout even to compare.
  • A checkout with no origin is registered as local-only. There is no push, fetch or pull request support for it until you add one: git -C .repolane/repos/<name> remote add origin <url>. lane done treats such a repo as having nothing to push and finishes the lane; see lane done refusing.

Starting work

lane start ABC-123 web api        # assumes you have already run `lane add` for repos named web and api
cd lanes/ABC-123 && claude

lane start creates branch ABC-123 in both repos, a worktree of each under lanes/ABC-123/, and a lane file at .repolane/registry/lanes/ABC-123.md that records the goal and which repos are in scope. The branch is created from the repo’s default branch. If a branch of that name already exists in the repo, it is checked out as it is and the base is ignored.

lane start also writes a CLAUDE.md, a .gitignore, an anchor .git and a .claude/settings.local.json at the lane root, a .claude/settings.local.json in each worktree, and an editor workspace file. Env files are symlinked in from the base clone.

Keep your workspace in your own git repo

The workspace is where your registry, memory and notes live, so it is worth versioning and backing up. Repolane never pushes it anywhere; the repo and its remote are yours.

lane init --git                 # git init, plus a .gitignore for the machine-local parts
git add -A && git commit -m "my workspace"
git remote add origin <your-private-repo> && git push -u origin main

lane init --git ignores what is rebuilt on each machine: /lanes/, /.repolane/repos/, /.repolane/cache/, /.repolane/registry/.shared-cache/ (the copy lane rules pull keeps), /.claude/settings.local.json and the editor workspace file /lane.code-workspace.

commit these: they are yoursRepolane refreshes these: commit them, never edit them
CLAUDE.md.repolane/REPOLANE.md
.claude/settings.json.repolane/LANE.md
.repolane/config.yml, .repolane/env.sh.repolane/version
.repolane/knowledge/, .repolane/memory/
.repolane/registry/repos.yaml and registry/lanes/

Lane files and old-checkouts.json can hold absolute paths from the machine that wrote them. Use a private remote.

On another machine

Clone your workspace repo, install Repolane, and run lane init in it:

pipx install repolane
git clone <your-private-repo> ~/work && cd ~/work
lane init                       # makes lanes/, the base clone folder and the cache, links memory, registers it
lane add <git-url>              # once per repo in .repolane/registry/repos.yaml: clones its base clone again
lane resume <id>                # recreates a lane's worktrees on their recorded branches

lane add keeps the entry repos.yaml already has for the repo. A branch that only ever existed on the other machine is not on the remote, so push lane branches you want to continue elsewhere.

Upgrading: lane upgrade

lane upgrade --check            # installed version, newest on PyPI, which workspaces are behind; changes nothing
lane upgrade                    # upgrade the package, then refresh every registered workspace

lane upgrade upgrades the package the way it was installed (pipx upgrade repolane, or uv tool upgrade repolane; for a pip install it prints the pip command to run), then refreshes every workspace in planes.json with the new version: the rule files and data migrations, Repolane’s hooks in .claude/settings.json (your own keys and hooks kept), Repolane’s slash commands and agents in .claude/, and every lane’s settings.local.json and CLAUDE.md. Nothing you wrote is changed. It reports each workspace and exits non-zero if one could not be refreshed.

A registered folder that is gone is skipped and reported, and you are asked whether to drop it from the registry (--drop-missing or --yes drops it). Run inside a workspace that is not registered, it registers it. --refresh-only skips the package step. A workspace you did not upgrade this way refreshes itself on its first lane command or hook after the installed version changes.

Taking it off

There is no lane uninstall. What it changes on your machine lists every file and the steps to remove it.

Read Concepts next. Release notes.

Next steps

  • ConceptsThe six words the rest of the docs use: workspace, repo, lane, worktree, reference, memory.
  • CommandsEvery lane command, grouped by what you are doing.
  • Moving from a git-clone installHow lane migrate turns an older clone install into a workspace.
  • Undoing itHow to remove what the package and lane init created.