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. ✅
Brings #37 (PR links, luv rm), #38 (terminal restore) and #39 (listing sessions started on other machines) under the slugged naming scheme. The two sides were mostly additive; what needed deciding: - Two workspace_number definitions. main's parsed a trailing -N off any folder name; this branch's reads the slug too but must be anchored on a known repo. Both are needed, so the repo-agnostic one is now folder_number — used by the rm -rf guard, the orphan scan and --clean, which only ever have a directory listing to go on. - The PR head query. A workspace's branch carries the slug of the machine that made it, so luv-{N} would find nothing for a slugged folder. fetch_pr now takes the branch, and workspace_branch reads it back off the folder name — slugged or pre-slug, ours or handed over. - Adopting a session another machine started. It used to guess the repo from the folder name, which '{repo}-{machine}-{N}' cannot support: splitting it needs one of the two parts up front. The origin lookup that already runs for the org now supplies the repo as well. - Dispatch tracked two different things under one name. This branch's `number` drives resolve_remote_workspace (the folder name is the remote's to know); main's `pr_hint` records a PR pinned at dispatch by -l/-pr. Both are kept, and meta carries model and pr_hint together. - parse_github_url and github_org_repo were the same helper under two names; one survives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJ8dKKVtXvyL28o1nfc91G
|
Rebased onto main by merging it in: brings #37 (PR links, Conflict resolutions worth a look:
128 tests pass, including new coverage for the slugged PR branch and slug-safe adoption. |
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