Summary
/session-flow:handoff writes the save-point file to an absolute location but emits a rootless
repo-relative path in the copy-paste resume prompt. When the resuming session's cwd is not the root
of the repository the handoff was written in, that path resolves against the wrong root. If a
.work/handoffs/ directory happens to exist under that wrong root — which it does on any machine
that has run /handoff from the home directory before — the failure presents as "the file is
missing" rather than "the path has no root", which is the most expensive shape to diagnose.
The same single-root assumption sits in /session-flow:find-handoff, the skill that exists
specifically to recover this situation, so the designated recovery path does not recover it either.
Observed occurrence
Session e67f9589-6925-4fc0-901e-41e4c0abec05, session-flow 0.17.14.
The session's cwd was C:\Users\<user> (not a git repository); all of its work was in a separate
local-only repository, D:\repos\...\claude-code-proxy. The transcript records both facts: every
cwd field in it holds either C:\Users\<user> or the session's scratchpad temp directory — the
worked-in repository never appears as a cwd.
The write went to the worked-in repo, which is reasonable (see "The write location is not the
defect"):
D:\repos\...\claude-code-proxy\.work\handoffs\20260726T234832Z-handoff-devils-advocate-applied.md
The emitted resume prompt, verbatim from the transcript's assistant text event:
`/clear`, then copy everything between the dashed lines:
──────────────────────────────────────────────────────────
Read @.work/handoffs/20260726T234832Z-handoff-devils-advocate-applied.md and continue its remaining next steps.
Prior session: e67f9589-6925-4fc0-901e-41e4c0abec05.
──────────────────────────────────────────────────────────
The fresh session started in the same cwd, C:\Users\<user>. That directory holds its own
.work/handoffs/ with 39 files matching *-handoff-*.md from unrelated sessions — so
@.work/handoffs/<file> resolved into a real, populated, wrong directory. The recovering session
reported four tool calls to recover the path (glob the wrong directory, search the home tree, grep
the prior transcript for the filename, grep it again for "file_path" to extract the absolute path);
that count is the recovering session's own report, not a figure this issue measured.
Note that the absolute root was available to the producer at write time and it did record it — inside
the handoff body: "Written against feat/logging-proxy at 5371e2d in
D:\repos\...\claude-code-proxy". Only the resume prompt, the one artifact the operator actually
carries across /clear, lost it.
The relative path is contract-specified, not a model slip
reference/save-point.md, "Emit the copy/paste resume prompt", full-path shape:
Read @<handoffs-dir>/<TS>-handoff-<topic>.md and continue its remaining next steps.
Prior session: <UUID>.
and immediately after it:
<handoffs-dir> is the path the write step actually used — the resolved
<memory_dir>/handoffs/ (default .work/handoffs/). Never emit a
default the file was not written to.
<memory_dir> is a repo-relative value by contract (memory_dir: .work in
.claude/topic-docs.yaml; default .work), so "the path the write step actually used" renders as a
rootless relative path. The guard that follows it is about the wrong axis: it prevents emitting a
memory_dir segment the write did not use, and says nothing about the root that segment hangs off.
skills/find-handoff/SKILL.md confirms this is the expected producer contract rather than a
one-off deviation, twice — in the detection ladder:
Apply the same placeholder filter as file mode … Confirm the referenced file exists on disk —
and resolve a relative directive path against the source transcript's cwd field, not the
current session's cwd: the producer emits repo-relative paths (e.g.
Read @.work/handoffs/…) …
and in Gotchas:
Relative directive paths resolve against the SOURCE transcript's cwd. The producer emits
repo-relative paths; a cross-repo recovery that checks existence from the current session's cwd
falsely reports the file missing.
So the model did what the engine specifies. The defect is that the specified output shape carries no
root anchor and nothing in the engine requires the emitted path to be resolvable from the resuming
session's cwd.
This is not only a cross-repo problem. @.work/handoffs/… also fails when the resuming session's
cwd is a subdirectory of the correct repository. Cross-repo is the worst presentation (a real-but-wrong
root that mimics a missing file), not the only trigger — a fix that special-cases cross-repo would
leave the subdirectory case broken.
Second surface: find-handoff has the same single-root assumption
Both rungs of the recovery ladder fail on this input.
Rung 1 — known-location glob. The skill's precompute is cwd-relative:
Default-location handoffs (this repo): !`ls -1t .work/handoffs/*-handoff-*.md 2>/dev/null | head -5 …`
and the ladder step filters candidates only on frontmatter and mtime:
Glob *-handoff-*.md, keep only files whose frontmatter is type: handoff, rank by mtime. A
strong, recent candidate → jump to step 4 …
Run from C:\Users\<user>, that glob returns 39 conforming type: handoff files from unrelated
repositories and the target is not among them. There is no repo-correlation check anywhere in the
rung — nothing in the text would reject a same-cwd, different-repo candidate. (Whether the model
would have short-circuited on one turns on its own read of "strong, recent"; the missing check is the
finding, not a predicted behavior.)
Rungs 2-3 — transcript scan. This rung does find the correct directive, and then discards it.
The rule quoted above resolves the relative path against the source transcript's cwd — which for
this session is C:\Users\<user> or the scratchpad temp directory, neither of which contains the
file — and the rung then requires "Confirm the referenced file exists on disk." The correct candidate
fails that existence check and is dropped. The rule written specifically to make cross-repo recovery
work is the rule that breaks recovery here, because it assumes the producer's cwd is the repo the
file was written into.
Anchoring the producer's emitted path fixes both surfaces at once: an absolute path needs no cwd
resolution, so rung 2-3's existence check succeeds without the cwd inference, and rung 1 stops
being the only path that can find a cross-root handoff.
The write location is not the defect
The write went to the repository the session was working in, which is the useful outcome — the
save-point lives with the work. Worth recording that it is nonetheless off-ladder: per the
topic-docs contract's "Resolution order", cwd C:\Users\<user> has no project root, and that branch
says interactive → ask (current directory or an explicit path); non-interactive →
${CLAUDE_PLUGIN_DATA}/topic-docs/handoffs/. The session was interactive and did neither; it
inferred the worked-in repo instead.
The gap is that the resolution order has no rung for "the session is working in a repository that is
not cwd's project root," so a correct-in-spirit write lands outside the ladder — and the render step
then has no root to render against. Deciding whether to add that rung is a separate call from
anchoring the path, and the path fix does not depend on it.
Proposed fix direction, and the tradeoff
The obvious candidate is to emit an absolute path in the resume prompt (keeping <memory_dir> as
the configured relative value for resolution, and rendering the resolved absolute path into the
directive). There is already precedent for absolute inside this engine's own contract:
reference/topic-docs.md specifies that the no-project-root fallback "lands handoffs under
${CLAUDE_PLUGIN_DATA}/topic-docs/handoffs/ with the absolute path announced prominently" — absolute
is already the answer on the one branch where relative has no anchor. This is the same condition,
reached a different way.
Two objections a reviewer will reasonably raise, and where they land:
- "No hardcoded/machine-local absolute paths." The topic-docs redaction bar ("committed evidence
is distilled — no machine-local absolute paths") and CLAUDE.md's repo-agnostic rule both govern
committed artifacts and plugin content. A resume prompt is neither: it is runtime output the
operator pastes on the same machine, and the handoff file itself is gitignored memory tier. The
handoff body already carries the absolute repo path today.
- Multi-machine / multi-checkout portability. This is the real tradeoff. An absolute path breaks
if the handoff is resumed on a different machine, or in a different checkout of the same repo — and
handoff's own "When to invoke" lists "Sharing state with another session or machine". A relative
path is portable if the resuming session's cwd is the repo root, which is exactly the condition
that failed here.
Shapes worth weighing rather than one asserted answer:
- Absolute path in the directive (simplest; loses cross-machine portability).
- Both — absolute directive plus the repo-relative path and repository identity as a stated line
below it, so a different-machine resume has what it needs to re-resolve.
- Keep relative but make the root explicit and machine-independent (e.g. name the repository /
remote / checkout the path is relative to, so a mismatch surfaces as a wrong root rather than a
missing file).
Whichever shape is chosen, find-handoff's detection contract moves with it — save-point.md
already states that changing the prompt format is a knowing break of that contract — and rung 1's
missing repo-correlation check should be closed in the same change.
Reproduction
- Start a session whose cwd is a directory that is not the repository the work happens in, and
which itself contains a .work/handoffs/ directory (a home directory that has run /handoff
before is the common case).
- Do work in a different repository and run
/session-flow:handoff.
- Observe: the file is written under
<worked-in-repo>/.work/handoffs/, and the rails prompt reads
Read @.work/handoffs/<TS>-handoff-<topic>.md.
/clear and paste. The @-reference resolves under cwd, into the unrelated .work/handoffs/, and
the file is reported missing.
- Run
/session-flow:find-handoff. Rung 1's glob returns the unrelated handoffs; rungs 2-3 locate
the correct directive and then discard it on the existence check.
Version verified
session-flow 0.17.14. reference/save-point.md, reference/topic-docs.md, and
skills/find-handoff/SKILL.md in the installed cache
(.claude/plugins/cache/melodic-software/session-flow/0.17.14/) are byte-identical to the versions on
main at fdb4dff, so the lines quoted above are the lines that ran.
Related
Summary
/session-flow:handoffwrites the save-point file to an absolute location but emits a rootlessrepo-relative path in the copy-paste resume prompt. When the resuming session's cwd is not the root
of the repository the handoff was written in, that path resolves against the wrong root. If a
.work/handoffs/directory happens to exist under that wrong root — which it does on any machinethat has run
/handofffrom the home directory before — the failure presents as "the file ismissing" rather than "the path has no root", which is the most expensive shape to diagnose.
The same single-root assumption sits in
/session-flow:find-handoff, the skill that existsspecifically to recover this situation, so the designated recovery path does not recover it either.
Observed occurrence
Session
e67f9589-6925-4fc0-901e-41e4c0abec05, session-flow0.17.14.The session's cwd was
C:\Users\<user>(not a git repository); all of its work was in a separatelocal-only repository,
D:\repos\...\claude-code-proxy. The transcript records both facts: everycwdfield in it holds eitherC:\Users\<user>or the session's scratchpad temp directory — theworked-in repository never appears as a cwd.
The write went to the worked-in repo, which is reasonable (see "The write location is not the
defect"):
The emitted resume prompt, verbatim from the transcript's assistant text event:
The fresh session started in the same cwd,
C:\Users\<user>. That directory holds its own.work/handoffs/with 39 files matching*-handoff-*.mdfrom unrelated sessions — so@.work/handoffs/<file>resolved into a real, populated, wrong directory. The recovering sessionreported four tool calls to recover the path (glob the wrong directory, search the home tree, grep
the prior transcript for the filename, grep it again for
"file_path"to extract the absolute path);that count is the recovering session's own report, not a figure this issue measured.
Note that the absolute root was available to the producer at write time and it did record it — inside
the handoff body: "Written against
feat/logging-proxyat5371e2dinD:\repos\...\claude-code-proxy". Only the resume prompt, the one artifact the operator actuallycarries across
/clear, lost it.The relative path is contract-specified, not a model slip
reference/save-point.md, "Emit the copy/paste resume prompt", full-path shape:and immediately after it:
<memory_dir>is a repo-relative value by contract (memory_dir: .workin.claude/topic-docs.yaml; default.work), so "the path the write step actually used" renders as arootless relative path. The guard that follows it is about the wrong axis: it prevents emitting a
memory_dirsegment the write did not use, and says nothing about the root that segment hangs off.skills/find-handoff/SKILL.mdconfirms this is the expected producer contract rather than aone-off deviation, twice — in the detection ladder:
and in Gotchas:
So the model did what the engine specifies. The defect is that the specified output shape carries no
root anchor and nothing in the engine requires the emitted path to be resolvable from the resuming
session's cwd.
This is not only a cross-repo problem.
@.work/handoffs/…also fails when the resuming session'scwd is a subdirectory of the correct repository. Cross-repo is the worst presentation (a real-but-wrong
root that mimics a missing file), not the only trigger — a fix that special-cases cross-repo would
leave the subdirectory case broken.
Second surface: find-handoff has the same single-root assumption
Both rungs of the recovery ladder fail on this input.
Rung 1 — known-location glob. The skill's precompute is cwd-relative:
and the ladder step filters candidates only on frontmatter and mtime:
Run from
C:\Users\<user>, that glob returns 39 conformingtype: handofffiles from unrelatedrepositories and the target is not among them. There is no repo-correlation check anywhere in the
rung — nothing in the text would reject a same-cwd, different-repo candidate. (Whether the model
would have short-circuited on one turns on its own read of "strong, recent"; the missing check is the
finding, not a predicted behavior.)
Rungs 2-3 — transcript scan. This rung does find the correct directive, and then discards it.
The rule quoted above resolves the relative path against the source transcript's
cwd— which forthis session is
C:\Users\<user>or the scratchpad temp directory, neither of which contains thefile — and the rung then requires "Confirm the referenced file exists on disk." The correct candidate
fails that existence check and is dropped. The rule written specifically to make cross-repo recovery
work is the rule that breaks recovery here, because it assumes the producer's cwd is the repo the
file was written into.
Anchoring the producer's emitted path fixes both surfaces at once: an absolute path needs no cwd
resolution, so rung 2-3's existence check succeeds without the
cwdinference, and rung 1 stopsbeing the only path that can find a cross-root handoff.
The write location is not the defect
The write went to the repository the session was working in, which is the useful outcome — the
save-point lives with the work. Worth recording that it is nonetheless off-ladder: per the
topic-docs contract's "Resolution order", cwd
C:\Users\<user>has no project root, and that branchsays interactive → ask (current directory or an explicit path); non-interactive →
${CLAUDE_PLUGIN_DATA}/topic-docs/handoffs/. The session was interactive and did neither; itinferred the worked-in repo instead.
The gap is that the resolution order has no rung for "the session is working in a repository that is
not cwd's project root," so a correct-in-spirit write lands outside the ladder — and the render step
then has no root to render against. Deciding whether to add that rung is a separate call from
anchoring the path, and the path fix does not depend on it.
Proposed fix direction, and the tradeoff
The obvious candidate is to emit an absolute path in the resume prompt (keeping
<memory_dir>asthe configured relative value for resolution, and rendering the resolved absolute path into the
directive). There is already precedent for absolute inside this engine's own contract:
reference/topic-docs.mdspecifies that the no-project-root fallback "lands handoffs under${CLAUDE_PLUGIN_DATA}/topic-docs/handoffs/with the absolute path announced prominently" — absoluteis already the answer on the one branch where relative has no anchor. This is the same condition,
reached a different way.
Two objections a reviewer will reasonably raise, and where they land:
is distilled — no machine-local absolute paths") and
CLAUDE.md's repo-agnostic rule both governcommitted artifacts and plugin content. A resume prompt is neither: it is runtime output the
operator pastes on the same machine, and the handoff file itself is gitignored memory tier. The
handoff body already carries the absolute repo path today.
if the handoff is resumed on a different machine, or in a different checkout of the same repo — and
handoff's own "When to invoke" lists "Sharing state with another session or machine". A relativepath is portable if the resuming session's cwd is the repo root, which is exactly the condition
that failed here.
Shapes worth weighing rather than one asserted answer:
below it, so a different-machine resume has what it needs to re-resolve.
remote / checkout the path is relative to, so a mismatch surfaces as a wrong root rather than a
missing file).
Whichever shape is chosen,
find-handoff's detection contract moves with it —save-point.mdalready states that changing the prompt format is a knowing break of that contract — and rung 1's
missing repo-correlation check should be closed in the same change.
Reproduction
which itself contains a
.work/handoffs/directory (a home directory that has run/handoffbefore is the common case).
/session-flow:handoff.<worked-in-repo>/.work/handoffs/, and the rails prompt readsRead @.work/handoffs/<TS>-handoff-<topic>.md./clearand paste. The@-reference resolves under cwd, into the unrelated.work/handoffs/, andthe file is reported missing.
/session-flow:find-handoff. Rung 1's glob returns the unrelated handoffs; rungs 2-3 locatethe correct directive and then discard it on the existence check.
Version verified
session-flow
0.17.14.reference/save-point.md,reference/topic-docs.md, andskills/find-handoff/SKILL.mdin the installed cache(
.claude/plugins/cache/melodic-software/session-flow/0.17.14/) are byte-identical to the versions onmainatfdb4dff, so the lines quoted above are the lines that ran.Related
bug(source-control/babysit-prs): readiness gate resolves repo from CWD, silently scores wrong PR cross-repo) — same class, different surface: a skill resolving a repository from cwd whencwd is not the repository in play. Not a duplicate.
.work/handoffsbypassingmemory_dirresolution in a retro snippet) but is about the
memory_dirsegment, not the root it hangs off.