Skip to content

Repo relocation silently loses commit trailers on 0.9.0: findSessionsForWorktree has no fallback when the recorded worktree path no longer exists (#1440 covers siblings only) #1890

Description

@jasonmx

What happened?

Moving a repository to a different absolute path silently stops commits getting an
Entire-Checkpoint trailer, for the rest of the session's life. Same root cause as #1852
(SessionState.WorktreePath recorded once, matched by exact string equality in
findSessionsForWorktree), but the fix that closed #1852 does not cover it.

#1440 added a fallback across sibling worktrees sharing a git common dir. A whole-repo
relocation is not that case: the recorded path is not a sibling worktree, and after the move it
does not exist at all, so there is nothing for the fallback to match.

I verified #1440 is present in this binary before concluding anything — a session started in a main
checkout does now link commits made in a linked worktree (control below). So this is a gap in that
fix, not an old build.

Four things make it hard to notice or recover from:

  1. The decline is DEBUG-only. prepare-commit-msg: no active sessions. The WARN added in
    fix(strategy): warn when ambiguous worktree sessions block commit linking #1856 does not fire, because it covers the ambiguous multiple candidates case. The
    zero-match case is the one that silently loses data, and it is still silent.
  2. entire status and entire session current disagree with the commit-time matcher. Run from
    the new path, both report the session as active, and session current — documented as "Show the
    active session for the current worktree" — returns it while printing Worktree: old-location,
    a directory that no longer exists. One code path considers the session to belong to this
    worktree; the other does not.
  3. The documented workaround cannot be applied. entire session adopt --from <old-path> is the
    remedy given on Worktree commits get no Entire-Checkpoint trailer when the session started in another worktree of the same repo (findSessionsForWorktree exact-path match) #1852, but it requires the source worktree to exist:
    resolve source git directory: fatal: cannot change to '<old-path>': No such file or directory.
    --from is mandatory (source worktree is required; pass --from <path>), so after a relocation
    there is no value that works.
  4. entire doctor reports everything OK and does not flag the stale worktree path.

Impact in my case: a repo moved mid-session, and roughly 12 commits across a 6.5 hour session were
left unlinked. The session itself never appeared in entire session list afterwards, so entire why reports the main design document as 100% human-authored when an agent wrote it. That is
provenance that is not merely missing but confidently wrong, which for a tool whose output people
rely on is the part that worried me.

A relocation is a fairly ordinary thing to do (renaming a project directory, moving it under a
parent folder), and unlike the sibling-worktree topology it gives no hint that session state is
path-keyed.

Steps to reproduce

Verified on 0.9.0. Full scripts drive entire hooks git prepare-commit-msg directly, the same
way #1852's reproduction did.

# 1. A repo with Entire enabled and a live session started in it
mkdir -p /tmp/repro/old-location && cd /tmp/repro/old-location
git init -q -b main
printf 'one\n' > file.txt && git add -A && git commit -q -m "initial commit"
entire enable --agent claude-code --no-github --checkpoint-backend refs -f

SID=11111111-2222-3333-4444-555555555555
T=/tmp/repro/transcript.jsonl
cat > $T <<EOF
{"type":"user","uuid":"u1","sessionId":"$SID","cwd":"/tmp/repro/old-location","gitBranch":"main","timestamp":"2026-08-03T09:00:00.000Z","message":{"role":"user","content":"add a line"}}
EOF

printf '{"session_id":"%s","transcript_path":"%s","cwd":"%s","hook_event_name":"SessionStart","source":"startup"}' \
  "$SID" "$T" /tmp/repro/old-location | entire hooks claude-code session-start
printf '{"session_id":"%s","transcript_path":"%s","cwd":"%s","hook_event_name":"UserPromptSubmit","prompt":"add a line"}' \
  "$SID" "$T" /tmp/repro/old-location | entire hooks claude-code user-prompt-submit

# 2. CONTROL: commit from the original path -> trailer is added
printf 'two\n' >> file.txt && git add -A
printf 'control commit\n' > /tmp/repro/msg1
entire hooks git prepare-commit-msg /tmp/repro/msg1 message
cat /tmp/repro/msg1
#   control commit
#
#   Entire-Checkpoint: 01KZ3ATEMW1PQTN7XY6A6HF6Q8      <-- present
git commit -q -F /tmp/repro/msg1

# 3. Move the whole repo. Nothing else changes.
cd /tmp/repro && mv old-location new-location && cd new-location

# 4. THE BUG: commit from the new path -> no trailer, no warning
printf 'three\n' >> file.txt && git add -A
printf 'commit after the repo moved\n' > /tmp/repro/msg2
ENTIRE_LOG_LEVEL=DEBUG entire hooks git prepare-commit-msg /tmp/repro/msg2 message
cat /tmp/repro/msg2
#   commit after the repo moved                        <-- no trailer, exit 0, nothing on stderr

Meanwhile, from the new path:

$ entire session current
Session 11111111-2222-3333-4444-555555555555
Agent:       Claude Code
Status:      active
Worktree:    old-location          # <-- no longer exists
Checkpoints: 0

$ entire session adopt $SID --from /tmp/repro/old-location --yes
resolve source git directory: fatal: cannot change to '/tmp/repro/old-location': No such file or directory: exit status 128

$ entire session adopt $SID --yes
source worktree is required; pass --from <path>

$ entire doctor
✓ Metadata branches: OK
✓ Claude Code hook config: OK
No stuck sessions found.

Control proving #1440 is present in this same binary (session started in the main checkout,
commit made in a linked worktree). Note that the Entire config has to be committed before
git worktree add, or the new worktree has no .entire/settings.json and the hook declines for an
unrelated reason — that caught me out first time:

mkdir -p /tmp/sib/main-checkout && cd /tmp/sib/main-checkout
git init -q -b main
printf 'one\n' > file.txt && git add -A && git commit -q -m "initial commit"
entire enable --agent claude-code --no-github --checkpoint-backend refs -f
git add -A && git commit -q -m "chore: enable entire"     # important
# ... start a session with cwd = /tmp/sib/main-checkout ...
git worktree add -q -b feature /tmp/sib/probe
cd /tmp/sib/probe
printf 'two\n' >> file.txt && git add -A
printf 'commit from a sibling worktree\n' > /tmp/sib/msg
entire hooks git prepare-commit-msg /tmp/sib/msg message
cat /tmp/sib/msg
#   commit from a sibling worktree
#
#   Entire-Checkpoint: 01KZ3AXWBDDDRAYMC68Z9SFJTM     <-- #1440 working

Suggestions

Only as starting points, since you know the constraints:

  • Match on something that survives relocation. The session store already lives in
    $GIT_COMMON_DIR/entire-sessions/, so any state found there is by construction the same repo;
    a "recorded worktree path does not resolve, and this is the only candidate" case looks safely
    adoptable without guessing.
  • Extend fix(strategy): warn when ambiguous worktree sessions block commit linking #1856's WARN to the zero-match case, or at least to zero-match-while-sessions-exist. That
    alone would have saved me a day, because the failure would have named itself.
  • Let adopt work without a live source: --from cannot be satisfied once the path is gone.
  • Have doctor flag session state whose WorktreePath does not exist.

Entire CLI version

Entire CLI 0.9.0 (Go go1.26.4, darwin/arm64)

OS and architecture

macOS 27.0 arm64 (Darwin 27.0.0), git 2.54.0

Agent

Claude Code

Terminal

Ghostty 1.3.1 (build 15212)

Logs / debug output

# prepare-commit-msg from the new path, ENTIRE_LOG_LEVEL=DEBUG
{"level":"DEBUG","msg":"prepare-commit-msg hook invoked","session_id":"11111111-...","component":"hooks","hook":"prepare-commit-msg","hook_type":"git","strategy":"manual-commit","source":"message"}
{"level":"DEBUG","msg":"prepare-commit-msg: no active sessions","session_id":"11111111-...","component":"checkpoint","strategy":"manual-commit","source":"message"}
{"level":"DEBUG","msg":"perf","component":"perf","op":"prepare-commit-msg","duration_ms":17,"steps.open_repository_ms":0,"steps.find_sessions_for_worktree_ms":11,"hook_type":"git"}

For contrast, the warning the real (non-synthetic) session emitted on every subsequent command was
session attribution diverged after recent history movement; figures may be off until next checkpoint. It names history movement rather than a path change, which is why I spent a day
looking at git history and hook installation before finding #1852.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions