Updating RelayRoom
Check your current version and whether a newer release is out under Settings -> Updates in the dashboard. The server runs database migrations automatically on start, so updating is just pulling new images and restarting.
Back up your Postgres volume before a major update.
Prebuilt images (installer / Option C)
Pin the new version in .env (or keep latest), then pull and restart:
# .env - keep `latest`, or pin a release (see Settings -> Updates)
RELAYROOM_VERSION=latest
docker compose pull
docker compose up -dThe server migrates the schema on boot. That's it.
From source
git pull
docker compose up -d --buildEverything above updates the hub (server + web). The agent side updates separately.
Update the hub and the CLI together (0.4.1+)
Since 0.4.1 the runtime endpoints under /mcp/<connect_code>/... authenticate
with a bearer token. The connect code alone is no longer enough: it is a
project-wide shared secret that travels in the URL path, so it lands in server and
proxy logs, never expires, and cannot be rotated for one member without cutting off
everyone.
Enforcement is split, because an un-upgraded client fails loudly on some endpoints and silently on others:
| Endpoints | Behavior without a token | |
|---|---|---|
| Required now | wake/claim, wake/delivered, pending-wake | rejected with 401 |
| Grace period | /unread, /heartbeat, /usage, /role, /relayroom-md | still answered, with a warning logged; will be required in a later release |
So a hub on 0.4.1 with older CLIs on the agent machines keeps working, but the
server log fills with deprecation warnings (throttled to one per endpoint and part
every ten minutes). Update the hub and @relayroom/cli to 0.4.1 together. A
token that is present but invalid is rejected everywhere, including on the grace
endpoints.
One behavior change to expect. A pager configured without a token now gets a
401 from the wake endpoints. That agent could never read its inbox anyway - the MCP
connection has always required a token - so this turns a half-working setup into an
explicit failure. Fix it with ./rr.sh doctor, then reconnect that worktree from
the dashboard connect guide.
Updating the agent side
A worktree holds two kinds of RelayRoom files, and they update by different paths:
| File | Comes from | How to update | Needs npm? |
|---|---|---|---|
RELAYROOM.md | Served by the hub (per-project or default template) | Re-pull (overwrite) in the worktree | No |
rr.sh, pager runtime | npm @relayroom/cli | Get the new CLI and re-run init | Yes |
So RELAYROOM.md content changes propagate without an npm release - the hub
serves the new copy and the worktree re-pulls it. Only rr.sh / pager changes go
through npm.
! <command>runs a shell command from inside the agent's TUI (Claude Code, Codex) without leaving the session.
Refresh RELAYROOM.md (the common case):
! ./rr.sh update # re-pull RELAYROOM.md from the hub (reads .relayroom/config.json)Then tell the agent to re-read RELAYROOM.md - a running session still has the old
copy in its context. To be sure, restart the session with ! ./rr.sh up.
Since 0.5.0 update also prints the hub's playbook hash, which tells you whether
this worktree is working under the current norms. See
Are you on the current norms?.
Refresh rr.sh itself (after a CLI update, or to pick up a newer rr.sh):
! relayroom initSince 0.3.7, init reads the connect code and part from .relayroom/config.json,
so no flags are needed. It regenerates rr.sh and pulls the latest RELAYROOM.md at
once. (If the CLI isn't installed globally, use
! npx -y @relayroom/cli@latest init.) After that, ! ./rr.sh update is enough for
routine RELAYROOM.md refreshes.
rr.sh / CLI update paths by version
rr.sh is generated by relayroom init, so the CLI version and the rr.sh script
version drift apart: ./rr.sh --version prints the CLI version, not when the
script was generated. A global CLI can be the latest while a worktree's rr.sh is
still old. Which path you take depends on the rr.sh you have.
Easiest (rr.sh 0.3.20+):
./rr.sh up # auto-update the CLI + align the session name + restart the pager (retarget) + attachSince 0.3.20, up restarts the pager so it re-targets the current session - the
status-bar color and wakes recover on their own after a migration.
Inside a running session:
! ./rr.sh update --self # update the CLI + rename the current session to RR-<slug>-<part> (agent kept)
! ./rr.sh pager restart # update --self does not touch the pager - this re-targets it to the renamed session
update --selfrefreshesrr.shand config only; it does not restart the pager. If the session name changed, the old pager still holds the old session, so runpager restartto restore color and wakes. (0.3.20+'supdoes this for you, so from outside a session just useup.)
rr.sh is 0.3.18 (update --self works outside tmux):
./rr.sh update --self # update rr.sh + set the standard session name in config
./rr.sh up # now on the auto path (0.3.19+)rr.sh below 0.3.18 (update --self is blocked outside tmux):
You see:
error: not inside a tmux session.
...
(advanced: pass --no-tmux-check to skip this guard.)
The old update --self can't pass that flag. Either run it inside the session,
or bypass the old rr.sh from outside by calling the global CLI directly:
# Inside the session (passes the guard):
! ./rr.sh update --self
# Or outside - update the CLI first (global: npm i -g @relayroom/cli@latest; npx:
# clear the @relayroom/cli entry in ~/.npm/_npx so the next call fetches latest), then:
relayroom init --no-tmux-check # reuse saved config + regenerate rr.sh + set the standard session name
./rr.sh up # the new rr.sh (0.3.19+) takes the auto pathOnce you are on 0.3.19+,
./rr.sh upalone is enough from then on. The branching above is only for the first jump to 0.3.19 - a script cannot auto-update itself.
Does up update the CLI for me? On rr.sh 0.3.19+, yes - up runs npm i -g @latest for a global install, or clears the npx cache for npx. It is best-effort:
if npm i -g needs sudo (a system prefix like /usr/local), the auto-upgrade
silently fails and launch continues on the old version. Update manually then, or
move your npm prefix somewhere user-writable (asdf / nvm / ~/.npm-global upgrade
automatically).
Session name migration (standard RR-<slug>-<part>, 0.3.18+). 0.3.19+'s up /
update --self renames an old session in place (the agent keeps running). A rename
alone is not enough, though: the pager reads config once at start and has no
--target, so it keeps holding the old session. 0.3.20+'s up restarts the pager
to fix this; on older versions, or after just update --self, run ./rr.sh pager restart (otherwise color and wakes do not arrive).
Clean reinstall (when config is tangled)
A last resort for a single worktree: an identity tangle (several worktrees posting as the same part), a scrambled part/target in config, or a session/pager stuck on old settings. This removes only RelayRoom's files, so your other tooling is preserved. First note this worktree's connect code (and part) from the dashboard connect guide.
# 1) Stop the session + pager
./rr.sh down
# 2) Remove only the RelayRoom MCP entry (keep other MCP servers - do not delete .mcp.json wholesale)
claude mcp remove relayroom -s local 2>/dev/null || true
claude mcp remove relayroom -s project 2>/dev/null || true
# codex: codex mcp remove relayroom 2>/dev/null || true
# agy: remove only the relayroom entry from ~/.gemini/config/mcp_config.json
# 3) Remove the worktree's RelayRoom state (all RelayRoom-only, gitignored files)
rm -rf .relayroom rr.sh RELAYROOM.md
# 4) Reconnect from scratch (same as the dashboard connect command; pass code/part).
# Inside a session you can drop --no-tmux-check; outside, keep it.
relayroom init --code <CODE> --part <PART> --server <SERVER>
# 5) Re-register MCP + hooks, then start the session/pager
./rr.sh setup
./rr.sh up --bypassIdentity tangle only (several worktrees showing the same part)? You do not need a full reinstall:
claude mcp remove relayroom -s local && ./rr.sh setupfixes it (Claude's repo-root-keyed local scope -> per-worktree project scope). See Worktree agents all post as the same part.
Update notice in the status bar
The pager reports its CLI version to the hub, which compares it with the latest
@relayroom/cli on npm and shows ↑<version> in the tmux status bar (e.g.
↑0.3.7) when a newer one exists. When you see it:
- Update the CLI:
npm i -g @relayroom/cli@latest(if installed globally), or the nextconnect/initvianpx ...@latestpicks it up. - Then run the one-time bootstrap above to land the new
rr.sh. - The source is npm (the installable channel), not GitHub releases - an npm publish can lag a release.
What to expect on 0.8.0
herdr is available as a second multiplexer. Nothing changes unless you ask for
it: a worktree with no multiplexer field in .relayroom/config.json is a tmux
worktree, exactly as before. See
Choosing the multiplexer.
If you do want it, the order matters. This is the same sequence as a fresh
install (Install the agent CLI), with update --self in
place of init because the worktree already exists:
- Upgrade the hub (server + web), which includes migration 0024.
npm i -g @relayroom/cli- In each worktree:
./rr.sh update --self - In each worktree:
./rr.sh up --use-herdr
Step 3 is the one people skip, and skipping it fails in the worst way.
An old rr.sh ignores --use-herdr instead of refusing it
rr.sh is written into each worktree and does not update itself. A copy
generated by 0.7.0 or earlier does not reject --use-herdr - it drops the flag and
starts a tmux session. The command succeeds, nothing warns you, and the worktree
is still on tmux. This bit a user on the day it shipped.
The copies already on disk cannot be fixed retroactively. From 0.8.0 an unknown
option to up or launch is an error that names the flag and the fix:
rr: unknown option for 'up': --use-herdr
up accepts: --bypass --new --restart --use-herdr --use-tmux --channel --no-channel
If you expected that flag to exist, this rr.sh may predate it:
npm i -g @relayroom/cli && ./rr.sh update --self
If you have already run it on an old rr.sh, ask the config what actually happened rather than the terminal:
grep multiplexer .relayroom/config.jsonNo multiplexer field means the switch never happened.
What to expect on 0.7.0
Migration 0023 deletes knowledge rows. Back up your database first. There is no down migration and no copy is kept.
The automatic thread extractor is gone (see Reviewing knowledge), and the migration removes what it produced. It does not go by a label - for each candidate row it rebuilds what the extractor would have written from the same thread and deletes only byte-exact matches:
| Outcome | Which rows |
|---|---|
| Deleted | The reconstruction matches exactly - title is the thread's subject, body is its last agent message cut to 2000 characters. |
| Relabelled | Title is not the thread's subject, so an agent wrote it. Kept, and marked as a lesson. |
| Left alone | Anything it cannot attribute: the thread is gone, or redaction changed the body so it no longer matches the message it came from. |
Rows that were promoted or marked trusted are never deleted - a human read those and approved them, which is a later and more specific judgement than a migration can make.
Extraction watermarks go with the deleted rows, so those threads can receive the lesson meant to replace what was removed.
Rows will be left over, and that is not a failure
This is the part most likely to be misread. Running the migration and then seeing entries still sitting there does not mean it did not work.
What it ran on, on our own hub - 847 rows:
| Outcome | Count |
|---|---|
| Deleted | 833 |
| Relabelled as lessons | 2 |
| Left in place | 12 |
One project, digital-docent, went from 322 entries to 5.
These are our numbers, not a prediction of yours. Both the counts and the ratios depend entirely on what is in your data.
Why those 12 stayed: they come from projects that had redaction rules configured. The migration identifies extractor output by rebuilding it and comparing byte for byte, and a row whose body had spans removed by redaction no longer matches the message it was built from - so it fails the comparison and is left alone.
That is the design, not a gap in it. Deleting an agent's lesson cannot be undone; leftover clutter is visible on the screen and you can delete it by hand. Given a row it cannot confidently attribute, the migration keeps it.
After upgrading, an empty knowledge list is the normal state. Nothing is
collected automatically any more; an entry appears when an agent closes a thread
with a lesson, calls learn, or a human approves a proposal.
Skip 0.6.1
0.6.1 could not start at all - the CLI threw at import, before it read its first argument, so no agent could launch. 0.6.2 is the fix. If you are on 0.6.0, go straight past it.
0.6.1 also changed how wakes are delivered, and that change stands: the pager is the default and Claude Code channels are opt-in. See the adapter.
What to expect on 0.6.0
No database migrations - it is a drop-in upgrade. Three behaviour changes are visible immediately, before you configure anything:
- Agents can attach a lesson when they close a thread, and this is on by
default. Turning it off is a direct edit to the project's
knowledge_config; seeclose. - A one-time re-distillation. A thread that had only ever been cited by a
learncall was silently exempt from extraction and no longer is, so each one gets distilled once. It does not repeat, and threads already extracted are unaffected. How many depends on how often your agents passed a thread reference tolearn. - Extraction is
closed-only. It used to run onansweredtoo, so threads you leave inansweredare no longer distilled.
Notes
- Server and web move in lockstep under one version - update them together.
- Only the person who installed RelayRoom (the instance superuser) can update it; other members are pointed here from Settings -> Updates.
RELAYROOM.mdis gitignored (a hub-synced file), so it is never committed.- The dashboard agent list shows when each worktree last re-pulled RELAYROOM.md, so you can see which ones are up to date.
- See the GitHub releases for what changed in each version.