Skip to content

fix(install): reject conflicting owners for an installed skill name #15

Closed
agents-tools/core
#12

Description

@showxu

Summary

Adding demo from two distinct source identities succeeds twice and overwrites the same installed directory. The resolved file retains two source pins claiming that path, so installed bytes no longer match the first owner's record.

Code evidence at 5d4987b:

  • Sources/Core/Installer.swift:152 sanitizes the name and :157 appends it directly to the flat canonical directory.
  • Sources/Core/Installer.swift:169 materializes the directory before merging records; :314 removes an existing destination without checking its source owner.
  • Sources/Core/Installer.swift:230 sets PinnedSkill.identity to that install name.
  • Sources/Core/InstallLock.swift:173 merges within source identity/location, allowing different sources to retain installations with the same destination.

Reproduction

Executed at 5d4987b.

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-skill-conflict"
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 name in source-a source-b; do
  write_skill "$repro_root/$name/demo" demo "$name"
  skill add "$repro_root/$name" --skill demo --agent codex --mode copy
 done
cat .agents/skills/demo/SKILL.md
isolated python3 - <<'CODE'
import json
pins=json.load(open('.agent/skills.resolved'))['pins']
for p in pins:
    print(p['identity'], p['skills'][0]['identity'], p['skills'][0]['installations'][0]['path'])
CODE

Observed output (relevant lines; temporary paths shortened to <temp>):

installed demo for codex at <temp>/project/.agents/skills/demo
installed demo for codex at <temp>/project/.agents/skills/demo
---
name: demo
description: Reproduction fixture
---
source-b
source-a demo .agents/skills/demo
source-b demo .agents/skills/demo

Expected Behavior

Detect an existing destination owned by a different source before changing files or records. Report both owners and the conflicting name.

Maintainer decision: choose whether intentional replacement needs an explicit operation or whether namespacing is supported. The immediate invariant is that a normal add cannot silently leave competing ownership records for one installed path.

Completion checks:

  • A conflicting add fails without altering the first installation or its resolved file.
  • Cover distinct source locations, sanitized-name collisions, canonical storage, and agent projections.
  • Re-adding/updating the same owner's skill remains supported.
  • Any explicit replacement reconciles the old record; run swift test.

Actual Behavior

Both adds exit successfully. The installed body is source-b; two pins still point to .agents/skills/demo. The sources are named source-a and source-b, so this reproduction does not depend on source-identity collisions.

Related: #4 concerns source membership updates, and #5 / PR #12 protect unreadable resolved files. This report starts with valid state and concerns competing installation ownership. No duplicate found.

Environment

  • Source revision: 5d4987b (5d4987b9e1f94e8b9f85124299f05d5aa4f45f07).
  • CLI version: 0.2.1; built successfully with swift 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions