Engineering
The .gitignore pattern that stopped matching when I moved a folder
A directory move silently un-ignored a file holding live credentials. The rule that caused it is one line of the gitignore spec, and the failure is completely silent.
I moved a directory this week and quietly un-ignored a file containing live credentials. Nothing failed. Nothing warned. git status simply began offering to commit a file it had been hiding for months, and if I had run git add -A without looking, the Supabase URL and key would have gone to a remote.
The cause is one sentence of the gitignore spec that I knew, had never had to think about, and got caught by anyway.
The move
The repository holds a Swift app. An Android app arrived, so the top level got lopsided — one platform in a folder, the other spread across App/, Checks/, Support/ and a couple of scripts at the root. I moved the Swift half under apple/, so apple/ and android/ sat side by side.
Pure git mv. No file contents changed. All the checks passed, both platforms built, and it looked like exactly the boring structural commit it was.
Then, out of habit, I looked at what git was proposing to commit:
?? apple/Support/pooled-flight/
?? apple/Support/telemetry.envBoth of those had been ignored an hour earlier.
The rule
Here is the relevant part of the .gitignore:
build/
*.xcodeproj
dist/
Support/telemetry.env
Support/pooled-flight/Five patterns. Three of them survived the move and two did not, and the difference is a slash:
If a pattern contains a slash anywhere except at the end, it is treated as relative to the directory containing the .gitignore file. Otherwise it matches at any depth.build/ has a slash only at the end, so it is a *name* — it matches build/ at any depth, including apple/build/. *.xcodeproj has no slash at all, so it matches anywhere too.
Support/telemetry.env has a slash in the middle. That makes it anchored to the repository root. It means <root>/Support/telemetry.env and nothing else. The moment the file became apple/Support/telemetry.env, the pattern stopped describing it, and the file became an ordinary untracked file that git was helpfully offering to add.
This is correct behaviour. It is documented. It is also completely silent: there is no warning when a pattern matches nothing, because a pattern matching nothing is a normal state for most patterns most of the time.
.gitignore was not edited. The tree grew a directory above them both, which is all it takes.Checking, rather than assuming
git check-ignore -v answers the question directly, and it names the pattern and line number that did the ignoring:
$ git check-ignore -v apple/Support/telemetry.env
# ← no output. Not ignored.
$ git check-ignore -v apple/build
.gitignore:1:build/ apple/build # ← still fineEmpty output means not ignored. That asymmetry — silence for the dangerous answer — is worth knowing before you rely on it.
The other question worth asking immediately, and the one I was most anxious about, is whether the file had ever been committed on any branch at any point:
$ git log --all --oneline -- '*telemetry.env'
# ← empty. Never committed.It had not. A near miss rather than an incident. If it had *not* been empty, the fix is very different and much worse: rotate the credential first, then worry about rewriting history, in that order and never the other way round.
The fix, and why it is the fix
The obvious repair is to re-point the patterns:
apple/Support/telemetry.env
apple/Support/pooled-flight/That works today and breaks the next time anything moves. It is the same bug, rearmed.
The better repair is to stop anchoring them at all:
telemetry.env
pooled-flight/No slash in the middle, so they match by name at any depth, wherever the tree goes next. Slightly broader than strictly necessary — any file called telemetry.env anywhere is now ignored — which for a secret is the correct direction to be wrong in.
What I would take from this
A gitignore pattern with a slash in it is coupled to your directory structure. For build output that is fine; if dist/ moves, you find out because your build breaks. For secrets it is a silent coupling to something you will eventually change for unrelated reasons, and the failure mode is that the secret becomes committable at the exact moment you are busy thinking about something else.
Prefer name-only patterns for anything sensitive. telemetry.env rather than config/telemetry.env. *.p12 rather than keys/*.p12. You lose a little precision and gain immunity to every future refactor.
Look at git status after a structural change, specifically for new untracked files. Not for the files you moved — git shows those as renames and they are fine. For the ones that appear from nowhere. Anything newly untracked after a move was previously ignored by an anchored pattern, and that is the entire class of this bug.
And run git check-ignore -v on your secrets after moving anything. It takes one command and it is the only way to get a straight answer, because the dangerous result is the one that prints nothing at all.
ExtendPilot shares a Mac's screen with the iPhones and iPads already in the room — as a mirror, or as a second desktop macOS treats as real hardware. Peer to peer over your own Wi-Fi, no account, no server in a session. It is not out yet. This one came out of restructuring the repo to hold an Android app alongside the Swift one.