CB-624: rename the local checkout to fleetd, and carry Claude Code's session and memory state with it #128

Open
opened 2026-08-22 21:35:13 +02:00 by ltms · 1 comment
Owner

Part of CB-621 (#125). Runs last of the move steps, alone, after CB-623 is proven.

Why it is its own ticket

A directory rename touches things a git transfer cannot: launchd, git worktrees, the operator's IDE config, and Claude Code's own state. Keeping it separate means a break can be traced to one step.

Everything that holds the old absolute path

what detail
deploy/dev.ltms.bridged.plist 5 lines hardcode /Users/dai.ha/LTMS/claude-bridge. The launchd service dies on rename.
the live worktree /Users/dai.ha/LTMS/.bridged-worktrees/cb401-manual. Worktrees store an absolute gitdir pointer, so it breaks.
CLAUDE.md:315 project_path = /Users/dai.ha/LTMS/claude-bridge/bridged
bridged/bridged.yaml gitignored, so no worker can see it. Holds fleet.leaders.opus.cwd and configDir.
.mcp.json tracked but flagged --skip-worktree (git ls-files -v returns S).

The part that is easy to miss: Claude Code's own state

Claude Code keys a project's saved sessions and memory by a slug made from the working directory, with / and . replaced by -. So today's state lives under:

~/.ccs/instances/ltms/projects/-Users-dai-ha-LTMS-claude-bridge/
    MEMORY.md
    memory/*.md
    <session-uuid>.jsonl

Rename the checkout to ~/LTMS/fleetd and Claude Code computes a different slug, -Users-dai-ha-LTMS-fleetd. It then finds no state and silently starts empty. Every saved session and every memory file is still on disk but unreachable.

So the rename must move that directory too, in the same step:

~/.ccs/instances/ltms/projects/-Users-dai-ha-LTMS-claude-bridge
 -> ~/.ccs/instances/ltms/projects/-Users-dai-ha-LTMS-fleetd

The memory files also contain absolute paths in their own text, including a pointer to /Users/dai.ha/LTMS/.bridged-handover/. Those are prose, not config, so they do not break anything, but they should be corrected in the same pass.

Scope

Write scripts/rename-checkout.sh with a --check mode, in the same shape as scripts/redeploy-bridged.sh. Doing this by hand is how one of the five surfaces above gets missed.

--check must report, changing nothing:

  • every file holding the old absolute path, with counts
  • whether the launchd service is loaded and which path it points at
  • whether the Claude Code project-state directory exists at the old slug and at the new one
  • whether any git worktree points at the old path
  • whether the daemon is running, and from which jar

The script must stop the daemon before moving anything and start it again afterwards, because a running daemon holds the old paths.

Acceptance criteria

  • bridge_whoami (or fleet_whoami after CB-622) still returns primary from a session opened in the new directory.
  • A previous session is resumable, and MEMORY.md loads.
  • git worktree list shows no broken entry.
  • The launchd service starts from the new path, and bridged.out shows a fresh listening line.
  • One worker spawns, gets a worktree, and opens a PR.

Order note

Do not reclone as a shortcut for any of this. A fresh clone drops the --skip-worktree flag on .mcp.json, and the operator's local file, which holds the IDE and forge MCP servers, gets clobbered at the next checkout.

Part of CB-621 (#125). Runs **last of the move steps**, alone, after CB-623 is proven. ## Why it is its own ticket A directory rename touches things a git transfer cannot: launchd, git worktrees, the operator's IDE config, and Claude Code's own state. Keeping it separate means a break can be traced to one step. ## Everything that holds the old absolute path | what | detail | |---|---| | `deploy/dev.ltms.bridged.plist` | **5 lines** hardcode `/Users/dai.ha/LTMS/claude-bridge`. The launchd service dies on rename. | | the live worktree | `/Users/dai.ha/LTMS/.bridged-worktrees/cb401-manual`. Worktrees store an absolute `gitdir` pointer, so it breaks. | | `CLAUDE.md:315` | `project_path = /Users/dai.ha/LTMS/claude-bridge/bridged` | | `bridged/bridged.yaml` | gitignored, so no worker can see it. Holds `fleet.leaders.opus.cwd` and `configDir`. | | `.mcp.json` | tracked but flagged `--skip-worktree` (`git ls-files -v` returns `S`). | ## The part that is easy to miss: Claude Code's own state Claude Code keys a project's saved sessions and memory by a slug made from the working directory, with `/` and `.` replaced by `-`. So today's state lives under: ``` ~/.ccs/instances/ltms/projects/-Users-dai-ha-LTMS-claude-bridge/ MEMORY.md memory/*.md <session-uuid>.jsonl ``` Rename the checkout to `~/LTMS/fleetd` and Claude Code computes a **different** slug, `-Users-dai-ha-LTMS-fleetd`. It then finds no state and silently starts empty. Every saved session and every memory file is still on disk but unreachable. So the rename must move that directory too, in the same step: ``` ~/.ccs/instances/ltms/projects/-Users-dai-ha-LTMS-claude-bridge -> ~/.ccs/instances/ltms/projects/-Users-dai-ha-LTMS-fleetd ``` The memory files also contain absolute paths in their own text, including a pointer to `/Users/dai.ha/LTMS/.bridged-handover/`. Those are prose, not config, so they do not break anything, but they should be corrected in the same pass. ## Scope Write `scripts/rename-checkout.sh` with a `--check` mode, in the same shape as `scripts/redeploy-bridged.sh`. Doing this by hand is how one of the five surfaces above gets missed. `--check` must report, changing nothing: - every file holding the old absolute path, with counts - whether the launchd service is loaded and which path it points at - whether the Claude Code project-state directory exists at the old slug and at the new one - whether any git worktree points at the old path - whether the daemon is running, and from which jar The script must **stop the daemon before moving anything and start it again afterwards**, because a running daemon holds the old paths. ## Acceptance criteria - `bridge_whoami` (or `fleet_whoami` after CB-622) still returns `primary` from a session opened in the new directory. - A previous session is resumable, and `MEMORY.md` loads. - `git worktree list` shows no broken entry. - The launchd service starts from the new path, and `bridged.out` shows a fresh listening line. - One worker spawns, gets a worktree, and opens a PR. ## Order note Do not reclone as a shortcut for any of this. A fresh clone drops the `--skip-worktree` flag on `.mcp.json`, and the operator's local file, which holds the IDE and forge MCP servers, gets clobbered at the next checkout.
ltms added this to the 2.0 — one operation centre, many hosts milestone 2026-08-22 21:35:13 +02:00
Author
Owner

Independent breakage inventory (unit B) — and what it caught that the script missed

Two workers ran this ticket in parallel and were deliberately kept apart: one wrote scripts/rename-checkout.sh (PR #143), the other inventoried the same rename read-only, without seeing the script. The point was to catch the author's blind spots. It worked.

Confirmed by both, and handled by the script

  • deploy/dev.ltms.bridged.plist — 5 hits (lines 47, 50, 56, 120, 122), and the installed copy at ~/Library/LaunchAgents/dev.ltms.bridged.plist is byte-identical today with the same 5 hits. Fixing one alone leaves the pair out of sync.
  • CLAUDE.md:315 — the IDE project_path. Prose, not runtime.
  • All four external git worktrees carry an absolute gitdir: back-pointer into the old checkout, so every one of them stops being a repo on rename.
  • Claude Code project state at slug -Users-dai-ha-LTMS-claude-bridge: 111 MB, 21 session .jsonl files (largest 40 MB), 4 session subdirectories, and memory/ with 47 files including MEMORY.md. The new slug does not exist.
  • bridged/bridged.yaml — gitignored, confirmed present, contents not read. Holds fleet.leaders.*.cwd and configDir.

Useful negative results: .mcp.json contains no absolute filesystem paths, so the rename does not break it; scripts/redeploy-bridged.sh derives its repo root from its own location and survives the move; .git/modules/wiki/config uses a relative worktree = ../../../wiki, so the submodule is fine as long as the tree moves as one unit; no symlinks resolve into the checkout; no hits in ~/.zshrc, ~/.zprofile, ~/.zshenv or crontab.

Three surfaces the script did NOT mention — all verified on the live machine by the lead

  1. ~/.claude.json has a projects map keyed by the literal old path. That entry holds hasTrustDialogAccepted, hasCompletedProjectOnboarding, enabledMcpjsonServers, allowedTools and lastSessionId. After the rename the new directory has no entry, so it comes up as an untrusted project with its MCP servers not enabled.
  2. ~/.config/herdr/session.json — 2 occurrences of the old path, live session records naming the old cwd.
  3. JetBrains — in IntelliJIdea2026.1 and IntelliJIdea2026.2: recentProjects.xml 3 hits each, trusted-paths.xml 2 hits each. Every other IntelliJ version on the machine is clean.

These are now being added to --check and to apply mode's closing summary as report-only, with the reason written into the script header so a later reader does not mistake it for an oversight:

  • ~/.claude.json is Claude Code's global config for every project on this machine and is written by live sessions; a bad edit there breaks far more than this repo.
  • herdr/session.json is live state of a running process.
  • The JetBrains files are rewritten by the IDE itself when the project is reopened at the new path.

The failure mode if the launchd agent is missed

KeepAlive {SuccessfulExit: false} with ThrottleInterval 10 means launchd restarts the dead job every ~10 seconds forever, against paths that no longer exist. That is the loop the plist's own CB-600 comment warns about.

Standing caveat

Every apply-mode branch in the script is unexecuted — correctly, since running it moves the directory the whole system runs from and stops the daemon the workers talk through. The script is well-reasoned, not proven. Its first real run is its test.

## Independent breakage inventory (unit B) — and what it caught that the script missed Two workers ran this ticket in parallel and were deliberately kept apart: one wrote `scripts/rename-checkout.sh` (PR #143), the other inventoried the same rename read-only, without seeing the script. The point was to catch the author's blind spots. It worked. ### Confirmed by both, and handled by the script - `deploy/dev.ltms.bridged.plist` — 5 hits (lines 47, 50, 56, 120, 122), and the **installed** copy at `~/Library/LaunchAgents/dev.ltms.bridged.plist` is byte-identical today with the same 5 hits. Fixing one alone leaves the pair out of sync. - `CLAUDE.md:315` — the IDE `project_path`. Prose, not runtime. - All four external git worktrees carry an absolute `gitdir:` back-pointer into the old checkout, so every one of them stops being a repo on rename. - Claude Code project state at slug `-Users-dai-ha-LTMS-claude-bridge`: **111 MB**, 21 session `.jsonl` files (largest 40 MB), 4 session subdirectories, and `memory/` with **47 files including MEMORY.md**. The new slug does not exist. - `bridged/bridged.yaml` — gitignored, confirmed present, contents not read. Holds `fleet.leaders.*.cwd` and `configDir`. Useful negative results: `.mcp.json` contains no absolute filesystem paths, so the rename does not break it; `scripts/redeploy-bridged.sh` derives its repo root from its own location and survives the move; `.git/modules/wiki/config` uses a **relative** `worktree = ../../../wiki`, so the submodule is fine as long as the tree moves as one unit; no symlinks resolve into the checkout; no hits in `~/.zshrc`, `~/.zprofile`, `~/.zshenv` or crontab. ### Three surfaces the script did NOT mention — all verified on the live machine by the lead 1. **`~/.claude.json`** has a `projects` map keyed by the literal old path. That entry holds `hasTrustDialogAccepted`, `hasCompletedProjectOnboarding`, `enabledMcpjsonServers`, `allowedTools` and `lastSessionId`. After the rename the new directory has no entry, so it comes up as an **untrusted project with its MCP servers not enabled**. 2. **`~/.config/herdr/session.json`** — 2 occurrences of the old path, live session records naming the old cwd. 3. **JetBrains** — in `IntelliJIdea2026.1` **and** `IntelliJIdea2026.2`: `recentProjects.xml` 3 hits each, `trusted-paths.xml` 2 hits each. Every other IntelliJ version on the machine is clean. These are now being added to `--check` and to apply mode's closing summary as **report-only**, with the reason written into the script header so a later reader does not mistake it for an oversight: - `~/.claude.json` is Claude Code's global config for every project on this machine and is written by live sessions; a bad edit there breaks far more than this repo. - `herdr/session.json` is live state of a running process. - The JetBrains files are rewritten by the IDE itself when the project is reopened at the new path. ### The failure mode if the launchd agent is missed `KeepAlive {SuccessfulExit: false}` with `ThrottleInterval 10` means launchd restarts the dead job **every ~10 seconds forever**, against paths that no longer exist. That is the loop the plist's own CB-600 comment warns about. ### Standing caveat Every apply-mode branch in the script is **unexecuted** — correctly, since running it moves the directory the whole system runs from and stops the daemon the workers talk through. The script is well-reasoned, not proven. Its first real run is its test.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#128