The thing that clicked today: a pattern that is safe in one repository can be dangerous in the next one for a reason that has nothing to do with the pattern, and "it is in the ignore file" only means what the tool that reads that file means by it.
Breadcrumbs is a browser extension; its source lives on a server and I load it into the browser on my laptop. Another project had solved the same delivery problem neatly: a sync folder from the server to the laptop, so every commit on the server lands in the laptop's extension directory automatically. Port the pattern, done.
Built / shipped
Why the port was not safe. The pattern worked in the first project because the shared folder was a subdirectory of the repository: version control's metadata sat above it and no secrets lived inside. In Breadcrumbs the extension sources sat at the repository root, and that root also held a 188 MB live, authenticated browser profile for the store the extension reads from, a secrets file, and the dependency tree. All three were in the version-control ignore file. The sync tool does not read that file. A direct port would have replicated a live session and a secrets file onto every machine in the mesh, which is exactly the threat the fleet's conventions describe. Only 47 files, 712 KB, of the 190 MB tree was tracked source.
Three options, one chosen. A denylist ignore file for the sync tool was rejected because it fails open: any future secret not on the list replicates silently. An allowlist fails closed, but silently drops new source files. I chose to restructure, so the exclusion is structural rather than a rule that has to keep being right: move the extension into its own subdirectory, share only that, and leave the profile, the secrets and version control above the share root where the sync tool cannot see them.
The file set derived from the manifest, not guessed: the nine files the browser's own extension manifest names. Moved with version control's rename so history is preserved. The sync folder created on the server end and accepted on the laptop through each side's API, receive-only on the laptop so nothing flows back, and the sync tool's own marker files added to the version-control ignore so the server's tree stays clean.
Verified. Both ends idle, nine files, 53,445 bytes, nothing pending, zero errors. Seven of nine files hash-compared across the two machines, all matching. The absence check that mattered: the secrets file, the browser profile, version-control metadata and the dependency tree all confirmed absent on the laptop. A round trip: a file added on the server appeared on the laptop; deleted on the server, the deletion propagated; back to nine, zero errors. Server tree clean after sync. Share root measures 76 KB across nine files, down from 190 MB.
Problems & fixes
The first project's share had drifted from its own spec in the meantime: its versioning setting on the server was empty where the original design specified one. Flagged for that project, not fixed here.
Decisions
Restructure rather than filter. An exclusion that depends on a list being complete is a future leak; one that depends on directory layout is not.
Derive the shared file set from the manifest the browser reads, so the share cannot drift from what the extension actually is.
Receive-only on the laptop. The server is the source of truth; the laptop is a mirror.
Learned
The ignore file belongs to version control, not to the filesystem. A second tool that copies files does not know what git has decided to forget.
Fails open versus fails closed versus structural: a denylist leaks the secret you did not list, an allowlist drops the file you did not list, and a directory boundary needs no list.
Verify absence as well as presence. The hash check proved the nine files arrived; the absence check proved the four things that must not arrive did not.
Still open / next
The same restructure should be checked against any other project whose extension or sources sit at a repository root beside anything sensitive, and the sibling project's versioning drift needs its own fix.