Persistent memory and session continuity for Claude Code across multiple machines.
Run Claude Code on your home PC and office PC — and, optionally, an always-on hub that keeps them in sync while you're away. Switch between machines, or run them at the same time, without losing context. No dedicated database. Just git, Node.js, and a CLAUDE.md.
v2: nodes may now run concurrently (not only in shifts), and you can add an optional always-on hub node. Safety comes from a strict pull-before-push rule plus per-node ownership and in-progress guards. See Concurrency & the hub.
Use a private git repository. The relay file contains your work context and task details. Never use a public repo with this setup.
You run Claude Code on two machines — home and office, laptop and desktop. Every time you switch computers, Claude starts cold. No memory of what it was doing. No memory of the other machine.
claude-toudou gives Claude Code persistent memory across machines by turning the two instances into a single agent that works in shifts.
When one machine's session ends, its Claude writes a structured handoff note. The other machine's Claude reads it, understands the intent, and continues without backtracking.
[home PC] [office PC]
│ │
Claude works │
Session ends │
│ │
writes handoff ──── git push ──────────► │
"decisions made, │
tasks pending, │
what to do next" │
git pull
Claude reads, understands, continues
│
writes reply ─── git push ──► [home PC]
In v1 the instances ran strictly in shifts. In v2 they may run concurrently — and an optional always-on hub can keep every machine current even while the others are off. What makes concurrency safe is the pull-before-push rule, not turn-taking.
Pull before push, always. A bare git push only sends your commits; it never pulls in
another node's work. Skip the pull and you eventually fork the relay file and your memory —
the exact failure mode v2 is built to prevent. Every sync, manual or scheduled, must run:
git pull --rebase --autostash → commit → push (retry once on rejection)
Use the included helper so you can't forget:
scripts/sync.sh # Linux/macOS — pull-first commit & push
scripts/sync.ps1 # Windows (PowerShell) — same, path-independentIt pulls first, commits, pushes, retries once if another node pushed in between, and skips itself if a sync is already running or the repo is mid-rebase/merge.
The optional hub. A spare always-on machine — home server, SBC, NAS, or an idle laptop —
runs scripts/sync.sh on a schedule (a few times a day, not constant polling) so every node
stays current without anyone being at a keyboard. The hub is just another node: same pull-first
rule, tags its own lines like everyone else. Add it to the hostname table and a cron entry; nothing else changes.
You need all of the following:
- Claude Code on two machines (home + office is the typical setup)
- You switch computers regularly and want session continuity
- Basic git skills (commit, push, pull)
- Node.js ≥ 18 on both machines
- Optional: a Telegram bot for cross-machine notifications
If you use Claude Code on one machine only, you don't need this.
Common search terms that led here: claude code multiple machines, claude code cross machine memory, claude code session continuity, claude code switch computers, claude code persistent context, claude code two computers
Without this: every time you switch machines, Claude starts from zero. With this: Claude reads the handoff note and continues where it left off.
Both instances share one Telegram bot. Either could send to your phone at any time.
This project keeps silence as the default: a desktop session sends nothing to Telegram
unless Claude explicitly calls notify-telegram.js, which queues the message for delivery via
GitHub Actions. No explicit call = no send, always.
Claude Code Routines run on Anthropic's servers, which block outbound connections to
api.telegram.org. Direct curl calls silently fail.
This project routes notifications through GitHub Actions — Claude pushes a pending
file to GitHub, Actions picks it up and sends the message. Works from any environment,
including when your PC is off.
Long sessions get compressed. Earlier context is lost. The included PreCompact hook injects 10 rules before each compression: extend prior summaries, never replace them.
python3 may be a Microsoft Store stub that silently fails in non-interactive contexts
(Task Scheduler, Claude Code hooks, shell scripts without a terminal).
Check which python3 you have:
(Get-Command python3).Source
# Bad: C:\Users\<user>\AppData\Local\Microsoft\WindowsApps\python3.exe ← Store stub
# Good: C:\Users\<user>\AppData\Local\Programs\Python\Python312\python.exeRecommended fix: Use the Node.js Telegram sender instead — it works reliably on Windows without any path configuration:
node scripts/telegram/send-telegram.js "your message"If you are behind a corporate proxy with a custom root CA, add --use-system-ca
(Node.js ≥ 22) so Node trusts the Windows certificate store.
If you need Python, use the full absolute path to python.exe (not python3).
The setup script will warn you automatically if a stub is detected.
Your relay repo must be private (it will contain your work context), so first create an
empty private repository on GitHub (e.g. my-claude-relay), then:
git clone https://github.com/Zhangshuzz/claude-toudou.git my-claude-relay
cd my-claude-relay
git remote set-url origin https://github.com/YOUR_NAME/my-claude-relay.git
git push -u origin main
npm run setupnpm run setup asks a few questions (machine names, hostnames) and outputs the exact JSON to add to your settings file.
That's the hardest part done.
Create an empty private repository on GitHub first, then:
git clone https://github.com/Zhangshuzz/claude-toudou.git my-claude-relay
cd my-claude-relay
git remote set-url origin https://github.com/YOUR_NAME/my-claude-relay.git
git push -u origin mainRepeat the clone (from your private repo) on the second machine.
npm run setupThe script will ask:
- Your workspace path (press Enter to use the current directory)
- A name for your first machine (e.g.
home,laptop) - A name for your second machine (e.g.
office,desktop) - Each machine's hostname (run
hostnameon it; press Enter to use this machine's, or fill in later)
It will:
- Update
CLAUDE.mdwith your machine names and hostnames - Update
relay/last-context.mdwith your machine names - Print the exact JSON block to add to your settings file (copy-paste ready)
Your settings file location:
| OS | Path |
|---|---|
| Windows | C:\Users\YOUR_NAME\.claude\settings.json |
| macOS / Linux | ~/.claude/settings.json |
Open the file and add the PreCompact, PostToolUse, and Stop entries printed by the setup script.
If "hooks" already exists, merge the new keys into it.
Restart Claude Code after saving.
Only needed if you want push notifications. Supports Telegram, Slack, Discord, ntfy.sh.
node scripts/telegram/setup.jsThe wizard asks which service you want, collects your credentials, and registers them as GitHub Secrets automatically. Takes about 2 minutes.
After setup, send a notification from anywhere (local session, Routines, PC off):
node scripts/telegram/notify-telegram.js "your message"
# Delivered via GitHub Actions in ~1 minuteWithout setup, the hook silently exits — nothing breaks.
npm run verifyExpected output: all checks passed (Node.js version, settings.json hooks, relay file).
For individual component tests:
npm run test:pre-compact # should print the rules block
npm run test:telegram-hook # should exit silently (no flag file = no send)
npm run test:stop-hook # should exit silently (relay not modified = no write)Restart Claude Code and open a new session. If the hooks are registered correctly, you will see no errors and sessions will work normally.
First session of the day:
git pull --rebase --autostash— get every other node's notes (pull-first)- Read
relay/last-context.md— lines tagged to you + shared tasks - Delete the lines tagged to you after processing (ownership rule)
- Work on incomplete tasks
End of session:
- Write a tagged line (e.g.
[→node-b]) with a summary inrelay/last-context.md - Run
scripts/sync.sh(orscripts/sync.ps1) — pulls first, then commits and pushes. Never bare-push: it would drop whatever another node pushed while you worked.
Same day, second session: read relay/last-context.md only. No pull needed.
relay/last-context.md— plain markdown, no dependenciesCLAUDE.md— plain text, no dependencies- The handoff protocol itself
These work regardless of Claude Code version updates.
| Component | Risk | How to fix |
|---|---|---|
Hook registration format (settings.json) |
Low — format is stable | Update key names if Claude Code changes them |
PreCompact hook event name |
Low | Update event name in settings.json |
context-hook.js Telegram API call |
Very low — Telegram API is stable | Update endpoint if Telegram changes it |
If a Claude Code update breaks something:
- Check Claude Code release notes for hook changes
- Open an issue — someone in the community likely hit the same problem
- The fix is almost always a one-line change in
settings.json
Yes. The hooks are under 100 lines each, with no complex logic. Any Claude Code user who can edit JSON can fix a hook registration.
For the target setup (two or three machines, one Claude session writing to the relay at a time), the relay protocol itself needs no maintenance — it's plain markdown with a fixed set of sections. That stops being true once you scale it up: running this at a higher node count with many automated processes writing to the relay frequently, we eventually hit file bloat, stale entries nobody cleaned up, and long-running notes that never got acknowledged. Fixing that took several small housekeeping scripts (staleness checks, forgetting-decay for old acknowledged notes, folding long entries down to a pointer) that aren't part of this project — they were specific to that heavier deployment. If your usage grows past casual two-machine handoffs, budget for writing the equivalent yourself.
Hooks not running:
- Confirm you saved
~/.claude/settings.jsonand restarted Claude Code - Run
node scripts/hooks/pre-compact.jsmanually — if it errors, fix the Node.js path - Check that paths in settings.json use the correct separator for your OS
Windows path errors:
- Paths in JSON strings inside settings.json need 4 backslashes:
C:\\\\Users\\\\... - The setup script generates the correct format automatically
Notifications not arriving:
- Run
node scripts/telegram/setup.jsto confirm the service is configured - Check GitHub Actions tab in your repo for workflow run status
- Test manually:
node scripts/telegram/notify-telegram.js "test"— should print "Queued" - If GitHub Actions fails: verify
NOTIFY_SERVICEand matching secrets are set (gh secret list)
Merge conflict in relay/last-context.md:
- Keep the version with more content
- Never drop another node's tagged lines
- Merge every node's lines back in manually, then commit
git pull fails on session start:
- Check network and git credentials
- The relay still works offline — just skip the pull and sync when back online
claude-toudou/
├── CLAUDE.md # Session rules, relay protocol, channel routing
├── .claudeignore # Keeps large files out of Claude's context
├── relay/
│ └── last-context.md # The AI-to-AI handoff file
├── scripts/
│ ├── sync.sh # Pull-first sync (Linux/macOS); the hub runs this on cron
│ ├── sync.ps1 # Pull-first sync (Windows/PowerShell)
│ ├── setup.js # Interactive setup helper
│ ├── verify.js # Installation checker (npm run verify)
│ ├── hooks/
│ │ ├── pre-compact.js # PreCompact hook: cumulative compaction
│ │ └── stop-hook.js # Stop hook: session end relay reminder
│ └── telegram/
│ ├── context-hook.js # PostToolUse hook: compaction notifier
│ ├── notify-telegram.js # GitHub Actions sender (works when PC is off)
│ ├── setup.js # First-run wizard: Telegram/Slack/Discord/ntfy
│ ├── send-telegram.js # Standalone direct sender (Node.js)
│ ├── send-telegram.py # Standalone direct sender (Python fallback)
│ └── .env.example
├── .github/
│ └── workflows/
│ └── send-telegram.yml # GitHub Actions: receive pending file → send → cleanup
├── .claude/
│ └── settings.template.json # Reference for hook configuration
└── docs/
├── relay-protocol.md # How the AI-to-AI handoff works
└── telegram-isolation.md # Why the flag pattern exists
- AI-to-AI by design — the relay file is written to be understood and acted on by Claude
- Pull before push, always — the rule that lets nodes run concurrently without forking
- Concurrency over turn-taking — safety from ownership + guards, not from forbidding overlap
- An optional always-on hub — keeps every node current on a schedule, no keyboard required
- No services beyond git hosting — the core relay needs only git and Node.js; the optional notification path uses GitHub Actions on the repo you already have
- Silence is the default — notifications only happen when Claude explicitly calls
notify-telegram.js - Works from any environment — local sessions, Web Routines, or when PC is off (via GitHub Actions)
- Compaction is cumulative — context deepens across compressions, not shallows
- Maintenance-free protocol — only hooks need occasional updates, not the core system
Built and first operated by Toudou (糖豆) — an AI agent named after a cat who lived for ten years.