Say you added *.png to .gitignore to keep design source images out of the repo. Months later you drop icon.png and opengraph-image.png into a Next.js app folder: the favicon shows up locally, but after deploying it is gone. No error anywhere. The same root shows up when you kill whatever lsof -i :3000 returns and your browser dies with the dev server, when a shell loop's 2>/dev/null makes grep never match, and when GA4 localhost and preview traffic lands in your production property.
Why does a broad pattern catch things you never meant?
When a selection rule keys on a surface name — a file extension, a port number, an output stream, a build mode — anything else that shares that name gets caught silently. What you wanted was "design source files", "the server listening on that port", "noisy warnings", "visits to the live site". But the rule targets a name that mostly overlaps with that property. The moment the overlap breaks, the pattern quietly takes the rest too. What it removes is an absence, not an error, so nothing reports it.

Four common faces
- Ignore lists: lines like
*.png,*.logorbuild/match every file with that name. In frameworks where the file name is configuration, such as Next.js,icon.pngis code. It exists on your disk, so development looks fine; it only goes missing from the commit. - Picking processes:
lsof -i :PORTlists processes that are connected to the port, not just the one that opened it. If a browser tab is talking to your dev server, the browser is on the list. - Discarding output: many CLIs write their human-readable tables to stderr, not stdout —
vercel ls, which lists deployments, does.2>/dev/nullthrows away "all of stream 2", not "warnings". A polling loop's grep never finds its line and runs to the end. - Splitting environments: putting the GA measurement ID in
.env.productionties it to the production build mode, not to production. A localnext startQA run and every preview deployment are production builds too. The more thorough your pre-release QA, the more it pollutes live numbers.
How do you narrow the rule?
Restate the target by a property only it has, not by its name, and put that property in the rule.
# Ignore list — by location, with explicit exceptions for convention files
/design/**/*.png
!app/**/icon.png
!app/**/apple-icon.png
!app/**/opengraph-image.png
# Only the server that opened the port — filter on LISTEN state
lsof -t -i :3000 -sTCP:LISTEN
# When parsing CLI output, merge streams instead of discarding one
vercel ls 2>&1 | grep Ready
When the property is "this is production", as with GA4, filter on the real production hostname, not the build mode. Environment variable branching alone cannot tell a local production build or a preview deployment from the live site.
// Only enable measurement on the production hostname
if (location.hostname === 'example.com' || location.hostname === 'www.example.com') {
gtag('config', GA_MEASUREMENT_ID);
}
What should you check before acting?
A narrowed rule can still be wrong. Before anything hard to undo — deleting, killing, discarding, sending — look at what the rule actually selected.
- After editing an ignore list or adding a file-convention resource, run
git statusandgit check-ignore -v <file>to confirm it is tracked - Before killing processes, print the list without
kill(lsof -i :3000 -sTCP:LISTEN). If the command column shows a browser, stop - Before adding a redirect that discards output, look once at what that stream actually carries (
cmd 2>&1 >/dev/nullshows only stderr) - After wiring an analytics tool, open a page on localhost and on a preview URL and confirm nothing appears in the realtime report. GA4 cannot wipe a specific day — a data deletion request only clears parameter values — so once polluted, your only option is to start reports from the next day
What happened to us
- Icons were missing only in production. A repo-wide
*.pngignore line droppedicon.png,apple-icon.pngandopengraph-image.pngfrom a Next.js app folder. Local was fine; we noticed becausegit statusnever listed those files. - Freeing a port killed the browser. Piping
lsof -i :PORTstraight intokillalso killed a browser helper process connected to the dev server. Filtering with-sTCP:LISTENleaves only the server. - A deploy-wait loop ran to its limit.
vercel lsprints its table to stderr, and a2>/dev/nullmeant grep never saw "Ready". The same loop timed out in three separate sessions. - QA traffic leaked into live numbers. With the measurement ID in
.env.production, a pre-release QA pass on a local production build showed up in the live property's realtime report that day. We then gated measurement in code on the production hostname.
In all four cases the command succeeded and nothing errored. The rule simply picked "things named like what we wanted" instead of "what we wanted".
PreviousNever let the attacker hold the reference — take values, rules and interpretation from where they cannot reach