Add machine slugs and luv handover - #36
Conversation
Workspace numbers come from GitHub's issue counter, which each machine
computes on its own — so a laptop and a box routinely pick the same one
and push the same branch. Workspaces are now {repo}-{machine}-{number}
and branches luv-{machine}-{number}, where {machine} is a short name for
the machine that created them (config 'machine', default: hostname).
The slug is stamped once and never changes, so it records where a
workspace came from. Pre-slug folders and branches keep working: lookups
try our own slug, then any other machine's, then the plain name.
Because the slug belongs to whichever machine holds a workspace, the
dispatcher can no longer compute a remote folder name. It takes it from
the session registry, else asks the host over ssh (luv --where), else
falls back to the existing luv-pending rename-on-arrival path.
On top of that, luv handover moves a session between machines:
luv --local myrepo "start something"
luv handover myrepo 43 --to box
The workspace is copied byte for byte — .git, dirty tree, untracked and
gitignored files — and the agent's transcript goes with it, with the
absolute path recorded inside rewritten for the new machine, so
claude --resume and codex resume --last continue the thread instead of
starting over. Bytes are relayed through the machine running the command,
so the two ends need no credentials for each other.
Everything checkable is checked before the agent is stopped, and the
source folder is never deleted without --purge, so a failed transfer is
always recoverable where it was.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UUYkpb2JohEeSpVK6TeeWf
|
Automated code review started - full review. Results will be posted here. |
|
⏳ Automated code review in progress...
|
| if old == new: | ||
| return "true" | ||
| return (f'for f in {files_expr}; do [ -f "$f" ] || continue; ' | ||
| f'sed {shlex.quote(f"s|{old}|{new}|g")} "$f" > "$f.luvtmp" ' |
There was a problem hiding this comment.
🟡 Warning: The rewrite_script function uses | as the sed delimiter (s|OLD|NEW|g), which will silently produce incorrect results if either path contains a | character. While uncommon in file paths, it is not impossible (some CI systems use pipe-delimited identifiers).
Fix: Use # as the sed delimiter instead — # cannot appear in POSIX filesystem paths, making it inherently safe:
return (f'for f in {files_expr}; do [ -f "$f" ] || continue; '
f'sed {shlex.quote(f"s#{old}#{new}#g")} "$f" > "$f.luvtmp" '
f'&& mv "$f.luvtmp" "$f"; done')
🔍 Automated Code Review — PR #36: Add machine slugs and luv handover📋 Executive SummaryThis is a high-quality, well-designed PR that solves a real collision problem (two machines picking the same workspace number) and adds a powerful new 📊 Change Architecturegraph TD
A["luv CLI (main)"]
B[Machine Slugs]
C[Workspace Naming]
D["Handover (cmd_handover)"]
E[Tar Relay]
F[Agent State Transfer]
G["Session Registry (sessions.json)"]
H["Remote Dispatch (dispatch_remote)"]
A -->|"new: --where, --paths"| B
B -->|"slug in name"| C
C -->|"backward compat"| G
A -->|"new: handover subcommand"| D
D -->|"stop agent + docker"| E
E -->|"tar relay through laptop"| F
F -->|"rewrite paths + resume"| H
D -->|"remove old, record new"| G
style A fill:#87CEEB
style B fill:#90EE90
style C fill:#FFD700
style D fill:#90EE90
style E fill:#90EE90
style F fill:#90EE90
style G fill:#87CEEB
style H fill:#87CEEB
Legend: 🟢 New | 🔵 Modified | 🟡 Breaking Change Risk 🔴 Breaking Changes✅ No breaking changes detected. The naming change from
|
hermes-exosphere
left a comment
There was a problem hiding this comment.
Automated review: Approved. ✅
All 73 tests pass. The naming change is backward-compatible, the handover implementation is carefully defensive (checks before acting, leaves source intact on failure, verifies git state after copy). One minor suggestion about sed delimiter safety — non-blocking.
Excellent test coverage: 36 new tests covering naming, handover, transfer, and remote resolution edge cases.
hermes-exosphere
left a comment
There was a problem hiding this comment.
Automated review: Approved. ✅
Workspace numbers come from GitHub's issue counter, which each machine
computes on its own — so a laptop and a box routinely pick the same one
and push the same branch. Workspaces are now {repo}-{machine}-{number}
and branches luv-{machine}-{number}, where {machine} is a short name for
the machine that created them (config 'machine', default: hostname).
The slug is stamped once and never changes, so it records where a
workspace came from. Pre-slug folders and branches keep working: lookups
try our own slug, then any other machine's, then the plain name.
Because the slug belongs to whichever machine holds a workspace, the
dispatcher can no longer compute a remote folder name. It takes it from
the session registry, else asks the host over ssh (luv --where), else
falls back to the existing luv-pending rename-on-arrival path.
On top of that, luv handover moves a session between machines:
The workspace is copied byte for byte — .git, dirty tree, untracked and
gitignored files — and the agent's transcript goes with it, with the
absolute path recorded inside rewritten for the new machine, so
claude --resume and codex resume --last continue the thread instead of
starting over. Bytes are relayed through the machine running the command,
so the two ends need no credentials for each other.
Everything checkable is checked before the agent is stopped, and the
source folder is never deleted without --purge, so a failed transfer is
always recoverable where it was.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01UUYkpb2JohEeSpVK6TeeWf