fix(ways): stop a symlinked way root from injecting every way twice - #454
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/...besidesoftwaredev/...). 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 strayhooks/ways/ways -> ~/.claude/hooks/wayslink 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 statere-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.shalready clears the marker onstartup/compact/clear.Verification
cargo test -p ways: 258 pass.$XDG_CONFIG/agent-ways/ways/wayslink: 24 headings, 0 duplicates, markers under the real ids (installed 1.9.0 doubled on the affected machine).