CB-624: report three more rename surfaces as report-only
CI / contract (pull_request) Successful in 55s
CI / build (pull_request) Successful in 1m14s

--check gains section 6 counting ~/.claude.json (the projects entry keyed
by the old absolute path), ~/.config/herdr/session.json, and JetBrains
recentProjects.xml / trusted-paths.xml across every IntelliJIdea* version.
Apply mode's closing summary lists the same three with manual follow-ups.

None of the three is rewritten automatically, on purpose: ~/.claude.json
is global live Claude Code config, herdr session.json is live process
state, and JetBrains rewrites its own files when the project is reopened
at the new path. Header comment states the reason for each so it does not
read as an oversight.

JetBrains stores these paths as its $USER_HOME$ macro rather than a
literal absolute path, so the counter matches both forms and says which
form the hits used.
This commit is contained in:
Dai Ha
2026-08-23 06:07:16 +02:00
parent d43f670285
commit 51bdec22e7
+110
View File
@@ -24,6 +24,22 @@
# /healthz AND demands a FRESH "bridged listening" log line dated after the restart, anchored
# to a line-count marker taken before anything moved.
#
# Three more surfaces hold the old path but this script REPORTS them and never rewrites them.
# That is a decision, not an oversight:
#
# A. ~/.claude.json — Claude Code's GLOBAL config for every project on this machine (the
# `projects` map keys projects by their absolute path). It is written by live sessions while
# this script could run, and one bad edit there breaks every project on the machine, not just
# this repo. The entry holds trust (`hasTrustDialogAccepted`), onboarding state,
# `enabledMcpjsonServers`, `allowedTools` and `lastSessionId`.
# B. ~/.config/herdr/session.json — live session records of a RUNNING process. Rewriting it
# underneath herdr corrupts its state.
# C. JetBrains options (recentProjects.xml, trusted-paths.xml under any IntellIJIDEA* version) —
# the IDE rewrites these itself when the project is reopened at the new path, so editing them
# by hand is both unnecessary and racy with a possibly-running IDE.
#
# For each of the three, --check prints the exact manual follow-up instead of fixing it.
#
# Usage:
# scripts/rename-checkout.sh --check # report what holds the old path; changes nothing
# scripts/rename-checkout.sh # stop daemon, move everything, restart, verify
@@ -45,6 +61,12 @@ OUT_NAME="bridged.out"
CC_INSTANCE="ltms"
CC_PROJECTS="$HOME/.ccs/instances/$CC_INSTANCE/projects"
# Report-only surfaces (see header, items A-C) — never rewritten by apply mode.
CLAUDE_JSON="$HOME/.claude.json"
HERDR_SESSION="$HOME/.config/herdr/session.json"
JETBRAINS_ROOT="$HOME/Library/Application Support/JetBrains"
JETBRAINS_FILES=(recentProjects.xml trusted-paths.xml)
LAUNCHD_LABEL='dev.ltms.bridged'
LAUNCHD_PLIST="$HOME/Library/LaunchAgents/$LAUNCHD_LABEL.plist"
@@ -104,6 +126,88 @@ report_hits() {
fi
}
# ---------------------------------------------------------------------------
# Report-only surfaces (header items A-C). Counted here, NEVER rewritten by
# apply mode — the header comment explains why each is left alone. Both
# --check (section 6) and apply mode's closing summary call this, so the two
# reports can never drift apart.
# ---------------------------------------------------------------------------
count_old_path_in_file() { # <file> <fixed-string-pattern> -> stdout: hit count (0 if file absent)
if [ ! -f "$1" ]; then echo 0; return; fi
# "no match" makes grep exit 1 and, under `set -o pipefail`, the whole pipeline too — so the
# assignment needs its own `|| true`; wc still prints the correct 0.
local n
n="$(grep -oF -- "$2" "$1" 2>/dev/null | wc -l | tr -d ' ')" || true
printf '%s' "${n:-0}"
}
report_only_surfaces() {
local n
# A. Claude Code global config — the per-project entry keyed by absolute path.
if [ -f "$CLAUDE_JSON" ]; then
n="$(count_old_path_in_file "$CLAUDE_JSON" "$OLD_PATH")"
KEY="unknown"
if command -v python3 >/dev/null 2>&1; then
KEY="$(python3 -c 'import json,sys
d = json.load(open(sys.argv[1]))
print("present" if sys.argv[2] in d.get("projects", {}) else "absent")' \
"$CLAUDE_JSON" "$OLD_PATH" 2>/dev/null)" || KEY="unknown"
fi
warn "A. $CLAUDE_JSON — old-path occurrences: $n; projects[\"$OLD_PATH\"] entry: $KEY"
echo " holds trust/onboarding state, enabledMcpjsonServers, allowedTools, lastSessionId."
echo " FOLLOW-UP: quit every Claude Code session first (this file is rewritten on exit),"
echo " then rename the key projects[\"$OLD_PATH\"] -> projects[\"$NEW_PATH\"], keeping the"
echo " entry's contents. If skipped: the trust dialog appears once at the new path and"
echo " .mcp.json servers start disabled until re-enabled."
else
ok "A. $CLAUDE_JSON absent — nothing to report"
fi
# B. herdr live session records.
n="$(count_old_path_in_file "$HERDR_SESSION" "$OLD_PATH")"
if [ "$n" -gt 0 ]; then
warn "B. $HERDR_SESSION — $n occurrence(s) of the old path (live herdr state)"
echo " FOLLOW-UP: stop the daemon/herdr FIRST, then edit the file (or leave it: sessions"
echo " started from the new checkout afterwards use the new path). Never edit while herdr runs."
elif [ -f "$HERDR_SESSION" ]; then
ok "B. $HERDR_SESSION does not reference the old path"
else
ok "B. $HERDR_SESSION absent"
fi
# C. JetBrains recent projects + trusted paths, any IntelliJIdea version.
# JetBrains stores these paths as its own "$USER_HOME$/..." MACRO, not as the literal absolute
# path, so a plain absolute-path grep reports 0 hits on files that are in fact full of them.
# Count both forms; report the macro form when that is where the hits live.
FOUND=0
JET_MACRO='$USER_HOME$'"${OLD_PATH#"$HOME"}" # e.g. $USER_HOME$/LTMS/claude-bridge
if [ -d "$JETBRAINS_ROOT" ]; then
for VDIR in "$JETBRAINS_ROOT"/IntelliJIdea*/; do
[ -d "$VDIR" ] || continue
VDIR="${VDIR%/}"
for F in "${JETBRAINS_FILES[@]}"; do
FPATH="$VDIR/options/$F"
ABS_N="$(count_old_path_in_file "$FPATH" "$OLD_PATH")"
MACRO_N="$(count_old_path_in_file "$FPATH" "$JET_MACRO")"
n=$((ABS_N + MACRO_N))
if [ "$n" -gt 0 ]; then
warn "C. ${FPATH#"$HOME"/} — $n occurrence(s) of the old path ($MACRO_N as the \${USER_HOME} macro, $ABS_N literal)"
FOUND=$((FOUND + 1))
fi
done
done
fi
if [ "$FOUND" -eq 0 ]; then
ok "C. no JetBrains IntelliJIdea* options file references the old path"
else
echo " FOLLOW-UP: open the project at $NEW_PATH in IntelliJ — the IDE rewrites"
echo " recentProjects.xml and trusted-paths.xml itself (it asks to trust once). Editing"
echo " these by hand is unnecessary and racy with a running IDE."
fi
}
# ---------------------------------------------------------------------- --check
if [ "$CHECK_ONLY" = 1 ]; then
@@ -189,6 +293,9 @@ if [ "$CHECK_ONLY" = 1 ]; then
warn "no daemon running"
fi
say "6. report-only surfaces (counted, NEVER rewritten — see header items A-C)"
report_only_surfaces
say "--check done: nothing changed"
exit 0
fi
@@ -370,5 +477,8 @@ fi
scan_old_path_in "$CC_PROJECTS" | head -5 | sed 's/^/ /' || true
echo " (session transcripts quote the old cwd — history, left untouched)"
say "left alone on purpose — report-only surfaces and their manual follow-up"
report_only_surfaces
echo
echo " Next: reconnect leads/members to the renamed checkout and re-check with --check."