Skip to content

Dedupe repeated installs to the same physical directory - #2156

Open
wakqasahmed wants to merge 2 commits into
vercel-labs:mainfrom
wakqasahmed:fix/symlinked-agents-dir-install
Open

wakqasahmed wants to merge 2 commits into
vercel-labs:mainfrom
wakqasahmed:fix/symlinked-agents-dir-install

Conversation

@wakqasahmed

Copy link
Copy Markdown

Summary

When multiple selected targets resolve to the same physical directory — several universal agents all writing to ~/.agents/skills, or an agent-specific directory that aliases to the canonical directory through a symlink — the install loop previously wiped and rewrote that directory once per target. This adds a cache keyed by the resolved write path, so a directory is only installed to once and every subsequent target that maps to it reuses that result.

Relation to #2098

This addresses one real contributing factor named in #2098: when ~/.agents itself is a symlink to another location (e.g. a Syncthing-synced drive), repeated rm+mkdir+write cycles against that target in quick succession are more likely to race with whatever else is watching or locking that path. Deduping to a single write per physical directory cuts that down substantially.

I want to be upfront about what this does and doesn't verify: I could not reproduce the exact reported EPERM: operation not permitted, stat '...\.agents' in a Linux sandbox — that error is Windows-specific and, per the issue, tied to a particular symlink + Syncthing interaction I don't have a way to trigger here. The included regression test confirms the dedup behavior itself (installing to 5 agents that all resolve to one symlinked universal directory succeeds and writes exactly once), not a reproduction of the original EPERM. If this doesn't fully resolve #2098 on Windows, it should at least reduce how often it happens — @TheWhiteDog9487, if you're able to test against this branch that would help confirm.

Testing

  • Added a regression test: installs a skill into 5 agents that all resolve to the same canonical directory via a symlinked ~/.agents, confirms the install succeeds and lands at the real (resolved) location.
  • Full suite: 774 passing, 3 pre-existing failures in detect-agent.test.ts unrelated to this change (that suite asserts on which agent is actually running the test process — fails in any non-Cursor sandbox regardless of this diff, confirmed by running it on a clean checkout).
  • tsc --noEmit clean, prettier --check clean.

Implemented with AI assistance (Claude Code).

@TheWhiteDog9487

Copy link
Copy Markdown

The situation is a bit strange, and I'm not sure exactly what happened.

Initially, when I tested using this PR's branch, I still encountered issue #2098. After I deleted ~/.agents and recreated it pointing to a newly created empty folder D:\test, the issue disappeared.

I then repointed ~/.agents back to the Syncthing-synchronized folder, and the issue reappeared. After that, I paused synchronization for this folder in Syncthing, and the issue disappeared again.

However, when I resumed Syncthing synchronization for this folder, the issue did not recur.

I also tried running bunx skills add directly, and the issue was gone as well.

I'm not really sure what happened exactly, and I can no longer reproduce the issue on my end now.

@wakqasahmed

Copy link
Copy Markdown
Author

Thanks for digging into that, even though it's inconclusive. That actually lines up with what I flagged in the PR description: the regression test I wrote passes on this Linux sandbox even without the fix applied, so I can't independently confirm it against the Windows-specific EPERM you originally hit. Syncthing pausing/resuming changing the outcome does point toward some kind of file-locking/timing race around the symlink-or-rename step rather than something purely deterministic in the code path, which would explain why it's hard to pin down on both our ends. If you do manage to get a reliable repro again, even a rough one, that'd help a lot more than anything I can construct here without a Windows+Syncthing setup.

@wakqasahmed

Copy link
Copy Markdown
Author

Hi @quuu — not sure who owns review for this one — it's been a little while, green and mergeable. Could you take a look or redirect me?

@TheWhiteDog9487

Copy link
Copy Markdown

I found the exact reproduction method for #2098, though it may not have much to do with this PR.
To quickly rebuild symlinks across multiple devices, I wrote a C# script in which I mistakenly used File.CreateSymbolicLink instead of Directory.CreateSymbolicLink to link ~/.agents to a folder managed by Syncthing.
After testing, the issue disappeared once the correct directory symlink was created using Directory.CreateSymbolicLink.

When multiple selected targets resolve to the same physical directory
(e.g. several universal agents all writing to ~/.agents/skills, or two
agent-specific dirs that both alias to the canonical dir via a symlink),
the install loop previously wiped and rewrote that directory once per
target. Cache the install result per resolved write path and reuse it
for every subsequent target that maps to the same directory.

This also reduces the number of separate rm+mkdir+write cycles issued
against a single physical directory in quick succession, which matters
when that directory is reached through a symlink to another location
(e.g. a Syncthing-synced drive) rather than a plain local path.
@wakqasahmed
wakqasahmed force-pushed the fix/symlinked-agents-dir-install branch from 2fbba80 to 501abfd Compare October 1, 2026 09:58
@wakqasahmed

Copy link
Copy Markdown
Author

@TheWhiteDog9487 thanks for tracking that down, that's a much cleaner root cause than either of us had.

I went back through this with your finding in mind, and I don't think the dedup fix in this PR is actually the same bug. The File.CreateSymbolicLink vs Directory.CreateSymbolicLink mixup happens before the CLI ever runs — that's in whatever script rebuilds the ~/.agents symlink itself, and the CLI just finds ~/.agents already there and resolves it with realpath/lstat. It never creates that top-level symlink. The one symlink the CLI itself creates (agent dir -> canonical dir in installer.ts's createSymlink) already passes 'junction' as the type on win32, so it isn't vulnerable to the same file-vs-dir mixup you found.

So this PR is still a real fix for a real thing (fewer redundant rm+mkdir+write cycles against one physical directory per add invocation), but it's addressing a different contributing factor than the one you eventually isolated, not the same bug restated. I can't verify from here whether the dedup reduces frequency enough to matter once ~/.agents is a correctly-typed directory symlink — with your repro fixed on your end there may be nothing left to even test against on Windows.

Given that, I'd treat #2098 as closed by your finding rather than by this PR, and this PR as a separate, smaller improvement that can land on its own merits. Rebased onto current main, no code changes beyond the rebase itself.

@wakqasahmed

Copy link
Copy Markdown
Author

Hi @quuu @antfu — checking back in — no reviewer yet, still quiet. Happy to make any changes needed.

This branch has not been deployed

No deployments
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.

[Bug]: Unable to install skill into a symlinked directory

2 participants