When you migrate a body of files that other things point at, the expensive part is never the files. It is the references. Move the files and leave a link behind at the old address, and the migration costs nothing.
The situation
A workspace had accumulated a real knowledge base without anyone designing one: three dozen architecture dossiers, a 190 KB incident log, a 219 KB log of database quirks, six hundred report files. It lived inside a code repository, and the code repository had been opened as an Obsidian vault — all 399,000 files and 9.8 GB of it, node_modules included. Search was useless. The graph was a hairball.
The fix was obvious: give the knowledge its own vault. The obstacle was less obvious. Roughly 294 references across 73 files pointed at those paths — instruction files that AI agents read, scripts that resolve paths at runtime, editor workspace configs, and years of reports citing each other.
Three of those files were named in a policy that said every session must append here before it ends. Break the paths and you don't get an error. You get agents writing to a location nobody reads.
The move most people make
Move the files, then grep for the old path and fix every hit. It works. It also means auditing 73 files to decide which references are load-bearing and which are historical prose that should keep saying what it said.
The move that costs nothing
Move the bytes to the new home. Then create a directory junction at the old path pointing at the new one.
<repo>\docs\project-knowledge -> <vault>\Knowledge
<repo>\memory -> <vault>\Memory
<repo>\reports -> <vault>\Reports
Every one of the 294 references still resolves, because a junction is transparent to anything that opens a path. The new vault owns the bytes and owns the version history. Only the handful of files that declare where the canonical location is needed editing — and those needed editing anyway.
Because the source and destination were on the same volume, the move was a rename. Six hundred files and 237 MB relocated instantly and consumed no additional disk, which mattered: the machine had 8.5 GB free.
The second read
Transparency cuts both ways, and this is where the technique earns its warnings.
Git cannot see a junction. On Windows it does not treat one as a link the way it treats a POSIX symlink. It walks straight through and indexes the target as ordinary files of the outer repo. After the move, git status in the old repository listed every migrated file as though nothing had happened. Two repositories now claimed the same bytes. The fix is to gitignore the mount points and untrack what moved — but nothing warns you. There is no message. You have to already know.
Syncthing has the opposite problem. It will follow the reparse point, and will happily sync the same tree once per mount point. The workspace already carried an anti-loop rule for exactly this reason, written by someone who had presumably learned it the hard way.
And a recursive delete follows the link. Remove-Item -Recurse on a junction does not remove the link. It removes the target's contents. The command that means “clean up this shortcut” means “destroy the thing it points at”. Use rmdir, which removes only the link.
So the technique is cheap, and its failure modes are all silent and all severe. That combination is worth respecting rather than avoiding: write the warning into whatever file your tools and your teammates actually read, and the sharp edge stays sheathed.
Where this generalises
Any migration where the movers are few and the referrers are many. Symlinks on Unix, junctions on Windows, CNAME records, HTTP 301s, database views over renamed tables, re-export shims in a package that moved. The pattern is the same: keep the old address answering, and the cost of moving collapses to the cost of moving.
The instinct is to update every pointer because leaving one behind feels like debt. Sometimes the pointer is the migration.
Leave a reply
This site has no comment database, so a reply opens in your own email app and comes straight to me.