Skip to content

fix(ways): stop a symlinked way root from injecting every way twice - #454

Merged
aaronsb merged 2 commits into
mainfrom
fix/duplicate-way-candidates
Aug 21, 2026
Merged

fix(ways): stop a symlinked way root from injecting every way twice#454
aaronsb merged 2 commits into
mainfrom
fix/duplicate-way-candidates

Conversation

@aaronsb

@aaronsb aaronsb commented Aug 20, 2026

Copy link
Copy Markdown
Owner

What

Two double-injection bugs in the scan path.

Symlinked way root → every way twice. A link inside one way root pointing at another root (or back at itself) made the symlink-following walk yield each way file again under a different id (ways/softwaredev/... beside softwaredev/...). Shadowing and markers key on the bare id, so both fired and rendered back to back: [A][B][A][B] on every UserPromptSubmit and PostToolUse, never SessionStart. Seen live on one install, where the old migrator's lift-user phase had carried a stray hooks/ways/ways -> ~/.claude/hooks/ways link into $XDG_CONFIG/agent-ways/ways/.

Fix: candidate collection tracks canonical paths across roots. A file resolving inside a different root is skipped there; one resolving inside its own root takes its id from the canonical path. Links resolving outside every root keep today's behaviour.

Stale-core heuristic → core.md twice on the first prompt. scan state re-showed core when the marker was >30 s old and the transcript <5 KB since the last summary. A fresh session's transcript is tiny, so a half-minute pause before the first prompt doubled core. Removed; clear-markers.sh already clears the marker on startup/compact/clear.

Verification

  • New tests: self-referential link yields each way once; a link into another root is collected by that root only. cargo test -p ways: 258 pass.
  • Release build run against a reproduced $XDG_CONFIG/agent-ways/ways/ways link: 24 headings, 0 duplicates, markers under the real ids (installed 1.9.0 doubled on the affected machine).
  • Stale-core repro (aged marker + empty transcript on UserPromptSubmit): no re-emit.
  • The affected install was also repaired directly (link removed, corpus rebuilt).

A symlink inside one way root that points at another root (or back at
its own) made the candidate walk, which follows links, yield every way
file a second time under a different id — `ways/softwaredev/...` beside
`softwaredev/...`. Cross-root shadowing and session markers both key on
the bare id, so both copies fired, rendered back to back, and stamped
their own markers: [A][B][A][B] on every UserPromptSubmit and
PostToolUse. One install had exactly this shape — the pre-1.0 migrator's
lift-user phase moved a stray `hooks/ways/ways -> ~/.claude/hooks/ways`
link into `$XDG_CONFIG/agent-ways/ways/`.

Candidate collection now tracks canonical paths across roots. A file
that resolves inside a different root is skipped (that root collects it
under its proper id); a file that resolves inside its own root takes its
id from the canonical path, so a self-referential link collapses onto the
first sighting. A link that resolves outside every root keeps its walked
id as before.

Also removes the stale-core heuristic in `scan state`: with the marker
older than 30 s and under 5 KB of transcript since the last summary it
re-showed core.md — which on a fresh session meant the first prompt
after a half-minute pause got core twice. `clear-markers.sh` already
clears the marker on the startup, compact, and clear matchers, so a
missing marker is the only signal needed.
Review remediation for the symlinked-root fix:

- `collect_from_dir` and `collect_checks_from_dir` were near-identical;
  one walker with a `Kind` now serves both, and the per-root parameters
  ride a `Walk` struct.
- Identity and shadow checks run before the file is read, so a duplicate
  sighting or a shadowed id costs no I/O.
- Two roots that resolve to the same directory are one root: neither is
  foreign to the other. Without this a user root symlinked to the core
  root made every file foreign to both and no way was collected.
- The self-referential-link test never reached the dedup path (walkdir
  reports the loop as an error, which the walk drops). It now aliases a
  sibling subdirectory, which does. A test for the same-directory roots
  case is added.
- ADR-174 quoted the removed stale-core heuristic as current behaviour;
  an amendment records its removal and why.
@aaronsb
aaronsb merged commit 22f185c into main Aug 21, 2026
11 checks passed
@aaronsb
aaronsb deleted the fix/duplicate-way-candidates branch August 21, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant