Skip to content

Commands

CommandWhat
initFirst-run wizard: remote (private), machine identity, roots, hooks
syncWalk → redact → manifest → tripwire → commit → push (--dry-run, --hook debounces)
pullFetch latest into the staging repo (no write to ~/.claude)
restoreRestore here, rewriting paths (--dir, --backup, --force, --prune); nudges a Desktop restart when Code sessions come back
statusSync state: remote, last sync, roots, hooks
repoRepo size, files, commits and history-vs-content ratio; repo gc repacks (no history lost), repo prune --before 2026-08-01 folds older history into one commit
searchFind a Claude Code session by title or content across live + synced history (alias grep); --since/--until/--cwd narrow, --raw grep lines, --all every file, --live/--repo scope, -s case-sensitive
recentList sessions newest first (alias last); dated by each transcript's own records rather than by file mtime, and labelled with the client + Desktop profile that ran each one. Takes an optional search term to narrow the window. --since (default 24h) / --until / --cwd / --account narrow, --limit caps, -l adds resume commands, --json emits the same list as records
ledgerReport the permanent session index; ledger backfill recovers rows for sessions pruned before it existed (-n dry run)
globalinstall / uninstall / status the global sync hooks in ~/.claude (alias hooks)
projectinstall / uninstall / status this repo's guard hook + CLAUDE.md guide (committed)
localsame as project, but gitignored (.claude/settings.local.json)
guardThe PreToolUse hook that enforces worktree/PR discipline — invoked by Claude Code, not run by hand (wired in by project/local)
guideinstall / uninstall / status / show the CLAUDE.md guide block standalone (--global targets ~/.claude/CLAUDE.md, --path overrides; install previews in a scrollable UI, skipped with -y or off a TTY)
mcplist (alias ls) / get / add / remove (alias rm) / enable / disable MCP servers (--scope user|project|local, --transport stdio|http|sse, --env, --header); list and get say whether each server travels to another machine, and list --json emits that as records; bare mcp on a TTY opens an interactive screen (mirrors claude mcp)
accountManage multiple Claude Code logins: add / list (alias ls/status) / run <id|email> [-- claude args] / switch / sessions (alias ps) / remove (alias rm) / purge. run --no-share isolates a session; switch takes --dry-run / --force / --kill
desktop (alias app)Several Claude Desktop accounts side by side, each in its own profile: add / open / list / quit / map / shortcut / prune / rm (alias remove). prune reclaims Electron caches and, with --vm or --all, the Cowork VM image or its whole bundle, without deleting the profile; --dry-run shows the breakdown
configget / set / show / path / edit (~/.clauderig/config.json)
doctorHealth-check environment + sync + worktree discipline, and report settings Claude Code ignores (--fix repairs what it can; ignored settings are advisory, fixed by editing the file or passing --permission-mode)
uiInteractive dashboard

The worktree and prune verbs (rig worktree, rig prune) live in rig — claudeRig wires the guard that makes them the default path. See Worktree discipline below.

The sync → restore loop

sync snapshots your ~/.claude setup, redacts secret-bearing fields, rewrites machine-specific paths into a portable form, and commits/pushes to your private repo. restore does the inverse on another machine: it pulls, rewrites the portable paths into this OS's slugs, and merges — keeping any local secrets in place so a new machine simply re-authenticates.

With plain storage (legacy backups or explicit opt-out), a session transcript is append-only and unbounded, and a long one is the single biggest thing a sync moves: committed whole every interval, a 50 MB transcript leaves a near-identical 50 MB blob in history each time. So past retention.largeFileBytes (8 MiB by default) a transcript is restaged only once it has grown by half that much again, or once it has gone quiet for half an hour — an active marathon session costs one blob per chunk of new content instead of one per sync, and a finished session's last turn is still captured. Smaller transcripts sync on every change, as before, and sync reports how many large ones are waiting. The SessionEnd hook runs sync --flush, which restages that session's transcript regardless (the hook names it), so a finished session's tail does not wait for the next one while other sessions' transcripts keep their throttle; a session that dies without firing it is caught up by the settle rule at the next sync. Run by hand, sync --flush restages every changed transcript; a hook payload that names no transcript flushes nothing, and sync says so rather than restage every long session's transcript on the strength of a broken hook. Set the key negative to restage every change.

Restore recreates shared-memory links only within the selected restore folder. It skips unsafe link paths and targets that lead outside that folder, including entries in older or manually edited backups. Restored links use relative targets so they remain valid if the folder moves.

When a restore brings back Claude Code sessions, it reminds you to fully quit and reopen Claude Desktop — Desktop only rebuilds its Code-tab list from the restored session sidecars on startup, so a running app won't show them until it restarts.

Transcript chunking

New configurations default to chunking on. Existing configs without the key use auto: they follow the repository, leaving an unversioned legacy backup plain until it is migrated. Explicit false opts out.

Upgrade every machine sharing the backup to a version supporting chunking first. Older clients can mistake a chunk index for a native transcript during restore. Then enable it and run a sync:

sh
clauderig config set chunkTranscripts true
clauderig sync

The next sync migrates existing staged transcripts larger than 8 MiB, including other machines' backups, into verified 4 MiB chunks. Completed chunks are reused; the changed tail is captured on every sync. Hook debounce still applies. Smaller transcripts stay native, and restore always reconstructs native JSONL. Search, peek and the ledger understand both formats. Divergent chunk-index conflicts require resolution; they are never combined as JSONL records.

Committed backup attributes prevent Git line-ending, encoding and filter conversions, including with core.autocrlf=true. Sync refreshes the Git index when installing those rules; overriding conversion attributes refuse publication. Previously damaged chunk history still needs an intact source or verified backup; hash checks remain strict.

Updated clients follow the repository setting unless locally overridden. clauderig config set chunkTranscripts auto removes the local override. To convert back, run clauderig config set chunkTranscripts false followed by clauderig sync, and clear other machines' true overrides. Rollback refuses before changing the tree if a chunked snapshot exceeds the configured native-file cap. Increasing that cap can exceed the remote's blob limit. Chunk mode requires retention.maxFileBytes of at least 4 MiB or no cap. Historical chunked revisions still require an updated binary after rollback.

Scanning checks complete staged text even when scrubbing is off. A recognized credential stops publication. clauderig config set redactTranscripts true scrubs supported signatures from staged transcripts, tool results, and memory notes before that check; private keys or signatures it cannot safely rewrite still cause refusal. Upgrading redaction restages those files once. Live files are never modified, and existing Git history is not sanitized by either setting.

Finding a session

"I was working on it yesterday"

When you can't remember a word from the chat but you know roughly when, recent lists sessions newest first with no search term:

sh
clauderig recent                    # the last 24 hours
clauderig recent webhook            # …narrowed to sessions that mention it
clauderig recent --since 7d --cwd acme-api
clauderig recent -l                 # full ids and resume commands
clauderig recent --json             # the same list as records, for a script
text
  today 09:12    a1b2c3d4  vscode           Refactor the auth middleware   feat/auth-split  ~/Git/acme-api
  yest. 17:40    9f8e7d6c  desktop@work     Fix the stale README table     main             ~/Git/acme-api
  ~yest. 13:22   f4501175                   (untitled session)

Each line is the client that ran it, the title, the git branch it ended on, and the project. Two of those columns exist because the project path so often identifies nothing — a session started one directory up, or one driving a worktree, tells you only that it was "in ~/Git".

The client column is qualified by Desktop profile. One machine can carry several Claude Desktop installs — the machine-wide one plus each clauderig desktop profile — and every one of them writes entrypoint: claude-desktop into the same shared ~/.claude/projects tree. desktop@work versus desktop@personal is the only thing that says which app to reopen a session in. recent and search read the sidecars of every profile, not just the machine-wide install, and take ownership from the accountUuid each sidecar is filed under rather than from the directory it happens to sit in — sidecars get copied between installs and keep their account path, so the directory is not ownership.

This matters because Claude Desktop lists only the sessions filed under the account it is signed in as. A profile can hold hundreds of another account's sidecars and show none of them, so a session will never appear in any Desktop but its own. -l says which one to open:

text
  Desktop session in the work profile — no other Desktop will list it:
  clauderig desktop open work

Give recent a word and it narrows the window instead of ranking the whole store, still in time order — it reads only the transcripts inside the window, so it answers immediately:

sh
clauderig recent --since 3d "connection pool"

Why not just sort by mtime

Because a file's mtime is not when you had the conversation. Restoring a backup, checking out the synced repo, or any tool that walks ~/.claude rewrites it — on one real machine, 580 of 670 transcripts had an mtime more than an hour newer than their last message, 541 of them stamped with the same minute by a single restore. Sorted that way, "most recent chat" means "most recently copied", and the handful you actually worked on yesterday are buried under hundreds that only look fresh. Claude Desktop's own session list is rebuilt from those same files and drifts the same way.

recent dates each session by the newest timestamped record in its own transcript. That is content, not metadata, so it survives every copy, sync and restore. A session with no timestamped record at all — ~/.claude holds a few stub files that never held a conversation — is still listed, but marked ~, so its date is never mistaken for the real thing.

"I remember what it said"

When you remember the words, search finds it by title or content:

sh
clauderig search "auth refactor"

It scans your Claude Code transcripts (projects/**.jsonl) across this machine's live ~/.claude and the synced staging repo — which may hold sessions from other machines or older ones no longer live here — then groups the hits into named sessions:

● Refactor the auth middleware
  a1b2c3d4 · 2026-07-02 · opus-4-8 · ~/Git/acme-api · desktop+repo
  17 match(es)   resume: cd ~/Git/acme-api && claude --resume a1b2c3d4-…
    …split the token check out of the request handler…

Each result carries the session's Desktop title, project, last-used date, and a ready claude --resume command. Titles are matched too, so a topic word finds the chat even when it isn't in the body, and results rank by relevance (title hit, then match count, then recency). Matches inside injected skill-listing and system records are ignored — so a word that appears in the skill catalog (like a skill name) doesn't light up every session.

  • --since / --until — narrow to when the session was last used: a day (2026-08-17), an RFC3339 timestamp, or an age (7d, 36h, 90m). Days are read in UTC, matching the date printed on each result, and a day given to --until covers that whole day
  • --cwd — narrow to sessions whose project directory contains this text
  • --raw — grep-style line output instead of grouped sessions
  • --all — search every file (config, skills, file-history, the Desktop dir), not just transcripts (implies --raw)
  • --live / --repo — restrict to this machine or the synced repo
  • -s / --case-sensitive

The narrowing flags describe sessions, so they can't be combined with --raw or --all (a date isn't a property of a grep line) — search says so rather than ignoring them. Sessions dropped by a filter are counted in the footer, and one that has no date at all is dropped by a time window rather than waved through, with its own count — so the totals always add up.

Sessions whose body has aged out

The synced repo is a rolling window: sync drops project transcripts older than retention.historyDays (90 by default). Without help, search then answers no matching sessions for anything older — which reads as that chat never existed, when in truth only its body left the window.

So sync keeps a ledger: index/<device>.jsonl, one row per session it has ever staged — id, title (the first prompt), project, date — written before retention runs, and never deleted. A row is a couple of hundred bytes; a several-hundred-session history is well under a megabyte.

Those sessions still answer a search:

● the auth refactor
  0f3a91c2 · 2026-03-04 · ~/Git/api · ledger
  title match   aged out of the synced window — body recoverable from the sync repo's git history

Only the title is searchable for them — the body isn't here to scan — and the note is deliberately not "gone": the blob is still in the sync repo's git history, which is the whole reason the row was kept. A session whose body is still staged is never labelled this way; it gets the ordinary synced copy only — restore on this machine to resume.

One file per device, because two machines appending to a shared file is a merge conflict on every sync. Rows are keyed by session id and unioned on read, newest row winning.

Backfilling what aged out before the ledger existed

The ledger only remembers from the day it is installed — every session pruned before that has no row. Their bodies are still in the sync repo's history though, because a deleted file leaves a git tree, not the history behind it:

sh
clauderig ledger backfill        # -n / --dry-run to see what it would recover
clauderig ledger                 # what it remembers now

backfill finds every transcript retention has removed, reads each one's head from the commit before its deletion, and writes the row. Rows already present are left alone — a live transcript is a better source than a deleted blob — so running it twice does nothing, and there is no reason to run it more than once. It writes into the staging tree; your next sync commits it.

Recovered rows carry a date from the last commit that touched the transcript, which is when it last changed rather than a timestamp read out of the body.

What history still holds

sync squashes the staging repo once it passes the size floor, and a squash prunes unreachable blobs — so bodies dropped before the last squash are gone, and backfill can only recover what history still carries. That is also why a ledger-only result says the body may still be recoverable rather than promising it.

Which machines the search could see

Absence of a hit is the answer people act on — that chat is gone — and it only holds if the store is complete. A machine that hasn't synced since Tuesday makes every Wednesday session invisible here, so search closes with the device roster and flags any machine it couldn't see:

1 session(s) match
scanned 1276 transcripts, skipped 0 binary
devices  mbp-16 8m ago (this) · air-13 3d ago
air-13 has not synced since 2026-08-16 23:39 UTC — anything it recorded after
that is not searchable here
  run `clauderig sync` there, then `clauderig pull` here

In the default scope, only other machines can hide anything — this one's live ~/.claude is scanned directly, so its own sync age costs the search nothing, and the roster is skipped entirely when yours is the only device (there is then no elsewhere for a chat to be).

Two scopes change that:

  • --repo searches only the synced repo, so this machine's live tree is not read: anything it has not yet synced is as invisible as another machine's, and it is warned about like any other device — including when it is the only one on the registry.
  • --live takes the synced repo out of scope, and with it any claim about other machines, so no roster is printed at all.

If the registry itself can't be read, the footer says device coverage unavailable rather than falling silent — silence there would be indistinguishable from a verified single-machine setup, which is the opposite conclusion.

On a long search a live scanning… N transcripts, K matches status shows on the terminal (and stays out of piped output).

Desktop "Chat" tab

Desktop Chat-tab conversations live server-side, not on disk — they never appear here. search covers your Claude Code transcripts; a miss doesn't prove a Chat-tab conversation is gone, so check claude.ai for those.

Opening one in Claude Desktop

Having found a session, clauderig desktop open --session carries it into Desktop's Code tab:

sh
clauderig desktop open work --session a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
clauderig desktop open work --session "auth refactor"   # title or project text
clauderig desktop open work -i                          # pick one from a list

You rarely know a uuid, so recent -l prints the command under each session, beside the terminal one — for the sessions it would work on, which means Claude Desktop installed, a profile saved, and a transcript in ~/.claude/projects here:

text
● Refactor the auth middleware
  a1b2c3d4 · 2026-05-14 · desktop@work · opus-5 · ~/Git/acme-api
  resume: cd ~/Git/acme-api && claude --resume a1b2c3d4-…
  desktop: clauderig desktop open --session a1b2c3d4-…

--session also completes to recent ids on <Tab>, each labelled with its title and project, and -i on its own opens a picker over the same list.

clauderig does not import anything itself: it hands Desktop a claude://resume?session=<uuid> link and the app's own handler reads the transcript. That is why the transcript has to be in ~/.claude/projects on this machine — a session that lives only in the synced repo must be restored here first. It is also why a second open profile makes the command refuse: a deep link is routed by scheme rather than to a window, so the OS would pick the recipient and could file the session under the wrong account. Quit the others, or pass --anyway when any window will do.

Getting back to the ordinary Claude Desktop

Once a profile window is open, the plain Claude Desktop is hard to reach: every instance is one application as far as macOS is concerned, so the Dock icon and Spotlight activate the window that is already there — the profile.

sh
clauderig desktop main     # open it, or bring it forward if it is already running

It is the only verb that touches the machine-wide install, and it says so every time it runs: no account is bound to it, open, quit and send cannot name it, and it competes for claude:// links like any other window. Its history is still backed up — sync walks it the same as any profile — but whose sessions those are is whatever that install is signed into.

The profile model this sits on is in docs/CLAUDERIG-DESKTOP-PROFILES.md.

Which MCP servers travel

An MCP server you added on this machine is not necessarily on the next one, and which ones survive the trip has nothing to do with the server and everything to do with the file Claude Code wrote it into. mcp list says so per server, in a TRAVELS column:

text
SCOPE    NAME       TRANSPORT STATE     TRAVELS    TARGET
user     railway    stdio     —         no         railway mcp proxy
project  tidy       stdio     enabled   your repo  npx -y @acme/tidy-mcp

Two verdicts, and neither of them is clauderig:

  • no — user and local scope live in ~/.claude.json, which sits beside~/.claude rather than inside it, so it is outside the sync root entirely. clauderig does not back these up. They work here and nowhere else.
  • your repo — project scope lives in the repository's own .mcp.json, and git is carrying that file, so it travels when the repo does, with no help from clauderig. An uncommitted or gitignored .mcp.json reads no instead, and unchecked means git could not be asked. What does not travel with it is the approval: whether you trusted the server is recorded in .claude/settings.local.json, which is gitignored, so a colleague who clones the repo is asked again.

That is the finding, not an omission in the column: every place Claude Code stores an MCP server is outside the tree clauderig syncs. A backup that ran nightly for a year carries none of them. The column exists to say so where you would look, rather than on the machine where you find out.

get prints the same verdict for one server, with the notes in full, and list --json carries them as records (portability.backedUp, portability.carrier, portability.notes[].kind) for a script that wants to gate on them.

Two notes are worth reading whatever the verdict says.

Every env and header value in a committed server is named — all of them, not the ones that look like secrets. clauderig treats those two maps as secret containers rather than judging each value, which is the right default for what it syncs and means the note does not rest on a guess. Arguments are different: most of them are ordinary, so an argument is named only when its value looks like a credential (--token=ghp_…). What clauderig cannot do either way is redact a file it does not sync: in .mcp.json these values sit in your repository in plain text.

Every absolute path is named too, including one under $HOME that clauderig could have written portably. That translation is something it does to files it carries, and it does not carry this one — git moves .mcp.json byte for byte, so /Users/you/bin/server is exactly as broken on a machine with a different home as /opt/homebrew/bin/server is. The server arrives defined and fails to start.

One more thing the column checks: whether this server is actually committed. Not just the file — a .mcp.json that is gitignored or was never added reads no, and so does a server you have added to a committed file but not committed yet, because a clone gets the commit rather than your working copy. If git cannot be asked, the column says unchecked rather than guessing.

Settings Claude Code ignores

Claude Code reads settings from three tiers — user (~/.claude/settings.json), project (.claude/settings.json, committed) and local (.claude/settings.local.json, gitignored) — and for most keys the narrower tier wins. Not for every key: a few are read from user or managed settings only. permissions.defaultMode: "bypassPermissions" (since the 2026-09-02 release) and "auto" are silently dropped from a project or local file, and nothing says so: a repo that committed the value when it worked just finds it no longer does. clauderig doctor reports such values as ignored settings, naming the file — and a defaultMode written at the top level of any tier's file rather than under permissions, or spelled in another case, which Claude Code never reads (JSON keys are case-sensitive). For a session that needs the mode, pass --permission-mode on the command line. Setting it in ~/.claude/settings.json works too, but that applies to every project on the machine — a much wider grant than the one repository the project file meant — so reach for it deliberately.

Hooks

sh
clauderig hooks install

Wires three Claude Code hooks: SessionStart → pull (so each session starts from the latest synced state), Stop → sync (so your work is captured after every turn), and SessionEnd → sync --flush (so a long session's last turn, which the per-turn sync may defer, is captured when the session ends). All are portable across OSes and idempotent.

Both hooks live in settings.json files, and Claude Code's --restricted mode (or CLAUDE_CODE_RESTRICTED=1) ignores every settings.json tier — user, project and local — so a restricted session neither syncs nor runs the guard. There is nothing clauderig can install that survives that; clauderig doctor warns when the variable is set in the environment it runs from, so the gap is visible rather than silent.

Worktree discipline

clauderig guard (a PreToolUse hook) and rig worktree make worktrees and PRs the default path for Claude Code, and stop a session from scrambling your VS Code chat history by moving its working directory. Chat history is keyed to the folder path, so the model edits from one pinned window while worktrees open in their own window for review only.

sh
rig worktree new <branch>   # sibling checkout off mainline (prints the path)
rig worktree new <branch> --open    # …and open a review window for this run
rig worktree new fix/x --base release-1
rig worktree list           # this repo's worktrees (alias: ls)
rig worktree open <branch>  # (re)open a worktree's review window (branch or path)
rig worktree rm <branch>    # remove the worktree, keep the branch (-f if dirty)

Worktrees live at <parent>/<repo>-worktrees/<branch> — a sibling of the repo, so they never clutter the primary checkout and each gets its own review-window history. new never moves the session's cwd; it prints the path and, when opted in, opens a separate window.

What the guard refuses

Anything that would put a worktree somewhere nothing later looks:

  • EnterWorktree / ExitWorktree — they move the session's own directory.
  • An Agent with isolation: "worktree", which makes a .claude/worktrees/<name> checkout through the Agent tool rather than by hand. An agent without isolation is an ordinary subagent and runs normally.
  • git worktree add aimed at .claude/worktrees. Listing and removing one there is allowed — that is the way out, not the way in.

clauderig doctor reports any that are already there. They are invisible to rig worktree list and never reaped by rig prune, so they accumulate unseen; one repo had 28 registered before anyone looked. The doctor names them and says what to run, and never removes one itself: a worktree can hold work that was never committed anywhere else.

Configuring the review window

Because worktree is a rig command, the review-window behavior is configured in .rig.json via rig config set, not in claudeRig. By default new does not open a window — opt in per run with --open, or always with the worktree.autoOpen key:

sh
rig config set worktree.autoOpen true       # always auto-open (like --open)
rig config set worktree.openCmd "cursor -n"  # open Cursor instead of VS Code
  • autoOpen (default false) — whether new opens a window at all. --open/--no-open override it per run; worktree open always opens.
  • openCmd (default code -n) — the program plus any flags; the worktree path is appended as the final argument and run directly (no shell). Examples: code -n, cursor -n, code-insiders -n, subl -n, idea.

See rig → Configuration for the full details. When the opener isn't on PATH, new/open print the command to run instead.

TIP

See the worktree-discipline doc for the guard rules and the full model.

TIP

See the design doc for the full picture.

Merge publication checks

clauderig merge and automatic reconciliation audit clean and conflicted merge results before committing, including a merge that could fast-forward. A secret refusal leaves the merge in progress. Clean and stage the affected files, then retry clauderig merge or clauderig sync, or use clauderig merge --abort. Unstaged tracked edits must be staged before retrying so the audit checks the bytes Git will commit. These checks do not remove secrets from existing history.

Released under the MIT License.