Repository navigation
Description
Summary
Different source locations can share one identity. Hosted URLs lose their host, and local paths use only the directory basename. Two local directories named skills merge into one watch record; hosted repositories with the same owner/path also produce the same cache key.
Code evidence at 5d4987b:
Sources/Core/SourceParser.swift:112derives owner/repository from the URL path;:130uses that host-free value, and:135uses the local basename.Sources/Core/ProcessRunner.swift:78derives the checkout directory solely from sanitized identity;:79reuses an existing checkout without validating its origin against the requested URL.Sources/Core/WatchLedger.swift:305derives a watch ID from identity;:308matches identity/ID and:331replaces the source and checkout while retaining the existing record.Sources/Core/InstallLock.swift:173additionally compares location when merging pins; the lock file itself does not universally merge same-identity locations.
Reproduction
Executed at 5d4987b. Local watch behavior and host identity parsing were both executed. The parser harness compiles the repository's source files directly and needs no hosted fixtures.
Run in Bash after cd "<checkout>" && swift build, replacing <checkout> with the absolute path of the checkout at this revision. Use a fresh reproduction directory. The commands isolate HOME, agent configuration, and XDG_*, and remove their fixture root on exit.
set -eu
checkout="<checkout>"
skill_bin="$checkout/.build/debug/skill"
repro_root="$checkout/.build/issue-repro-local-identity"
mkdir -p "$repro_root/home" "$repro_root/tmp" "$repro_root/project"
trap 'rm -rf "$repro_root"' EXIT
isolated() {
env -i PATH="$PATH" HOME="$repro_root/home" \
CODEX_HOME="$repro_root/home/.codex" \
CLAUDE_CONFIG_DIR="$repro_root/home/.claude" \
XDG_CONFIG_HOME="$repro_root/home/.config" \
XDG_STATE_HOME="$repro_root/home/.local/state" \
XDG_CACHE_HOME="$repro_root/home/.cache" TMPDIR="$repro_root/tmp" \
GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null \
GIT_AUTHOR_NAME=Fixture [email protected] \
GIT_COMMITTER_NAME=Fixture [email protected] \
"$@"
}
skill() { isolated "$skill_bin" "$@"; }
write_skill() {
mkdir -p "$1"
printf -- '---\nname: %s\ndescription: Reproduction fixture\n---\n%s\n' "$2" "$3" > "$1/SKILL.md"
}
cd "$repro_root/project"
for owner in a b; do
write_skill "$repro_root/$owner/skills/demo" demo "$owner"
skill add "$repro_root/$owner/skills" --watch-only --skill demo --agent codex
done
isolated python3 - <<'CODE'
import json
w=json.load(open('.agent/skills-state.json'))['watches']
print('watch count:', len(w))
for r in w:
print('watch ID:', r['watch_id'], 'location:', r['source']['location'])
CODE
cat > "$repro_root/main.swift" <<'SWIFT'
import Foundation
for input in ["https://github.com/a/b", "https://gitlab.com/a/b"] {
let parsed = try SourceParser.parse(input)
print(input, "=>", SourceParser.identity(for: parsed))
}
SWIFT
isolated swiftc -module-cache-path "$repro_root/module-cache" \
"$checkout/Sources/Core/CoreError.swift" "$checkout/Sources/Core/PathSafety.swift" \
"$checkout/Sources/Core/SourceRequirement.swift" "$checkout/Sources/Core/SourceParser.swift" \
"$repro_root/main.swift" -o "$repro_root/identity-test"
isolated "$repro_root/identity-test"Observed output (relevant lines; temporary paths shortened to <temp>):
watched skills path demo
watched skills path demo
watch count: 1
watch ID: skills location: <temp>/b/skills
https://github.com/a/b => a/b
https://gitlab.com/a/b => a/b
Expected Behavior
Distinct repositories must retain separate source identities, watch state, and cache entries. Repeated references to the same repository should remain stable.
Maintainer decision: choose canonical location normalization and a migration/ambiguity policy for existing watch IDs, pins, and caches. A cache hit must not silently bind a new source location to an unrelated origin.
Completion checks:
- Different hosts with the same owner/path remain distinct.
- Two local paths ending in
skillscreate independent watches and pinned-source caches. - Updating either watch preserves the other's location, baseline, and review cursors.
- Re-adding the same source remains idempotent; ambiguous existing state is handled explicitly; run
swift test.
Actual Behavior
Both watch adds succeed, but only the second location remains in the single skills record. The parser harness returns a/b for both hosted URLs. Cache-directory collision follows from the same identity-only cache construction; hosted repositories were not cloned in this reproduction.
Related: #10 tracks watch scope; these collisions occur within one project and remain even with a scope-correct ledger. No duplicate found.
Environment
- Source revision:
5d4987b(5d4987b9e1f94e8b9f85124299f05d5aa4f45f07). - CLI version:
0.2.1; built successfully withswift build. - Swift: Apple Swift 6.4 (
swiftlang-6.4.0.34.1,clang-2100.3.34.1). - OS: macOS 27.0.1 (26A434).
- Architecture: arm64.
- Git: 2.42.0. Validation date: 2026-10-17.
Activity