When Your Agent's Files Vanish Into .gitignore — A Pre-Commit Detection Gate
When an agent writes files that match .gitignore, the diff review looks perfect but nothing lands in the commit. Here is a gate script that catches ignored build output before you push, plus the tuning it needs.
I asked an agent to write generated HTML into public/content, reviewed the diff in the artifact viewer, and committed with confidence. Nothing showed up in production. When I traced it back, the file had never appeared in git status at all. public/content/ was in .gitignore.
It cost me most of a day. The agent really had written the file. It existed on disk. The diff UI displayed it. Only Git stayed silent — and that is exactly what makes the bug hard to see.
The agent's diff and Git's diff are not the same thing
Antigravity's artifacts show you every file the agent touched. That is a filesystem fact. git status shows you something narrower: changes that are tracked, or untracked but not matched by an ignore rule.
Aspect
Artifact viewer
git status (default)
Scope
Every file the agent wrote
Tracked + untracked non-ignored
Matches .gitignore
Shown normally
Completely silent
How failure looks
Success
Empty diff — as if nothing happened
"Agent succeeded, Git detected nothing" is indistinguishable from the happy path. Any repository that ignores build output will hit this the moment you actually want some generated file committed: prebuilt static assets, bundled content HTML, generated type definitions.
List only the ignored files
Git can surface ignored paths explicitly. The default --ignored collapses them by directory, so ask for matching when you need file-level granularity.
# List ignored paths, one file per line (!! marks ignored)git status --porcelain --ignored=matching | grep '^!!' | cut -c4-# Trace which rule ignored a specific pathgit check-ignore -v public/content/articles/en/agents/foo.html# → .gitignore:12:public/content/ public/content/articles/en/agents/foo.html
git check-ignore -v tells you the file and line number of the rule that matched. That output is how I finally found a stale exclusion sitting in .git/info/exclude rather than .gitignore — somewhere I would not have thought to look.
For a batch of paths, feed them through stdin:
# Pass the agent-touched file list, keep only the ignored onesprintf '%s\n' "${TOUCHED[@]}" | git check-ignore --stdin --verbose
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Listing exactly the agent-written files Git refuses to track, using git status --porcelain --ignored=matching
✦A 60-line pre-commit gate that exits 1 on ignored build output, with an allowlist escape hatch
✦The order to fix .gitignore in so false positives settle around 10% instead of drowning the signal
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Git cannot tell you which files an agent touched. The filesystem can. I drop a timestamp reference file before the run and collect everything newer afterwards with find -newer. You could watch with inotify instead, but for one-shot CLI invocations the reference-file approach breaks in fewer ways.
#!/usr/bin/env bash# agent-run-with-guard.sh — wrap an agent run and inspect what it producedset -euo pipefailREPO_ROOT="$(git rev-parse --show-toplevel)"cd "$REPO_ROOT"STAMP="$(mktemp)"trap 'rm -f "$STAMP"' EXITtouch "$STAMP"# Run the actual agent here"$@"# Collect files newer than the stamp, skipping .gitmapfile -t TOUCHED < <( find . -type f -newer "$STAMP" \ -not -path './.git/*' \ -printf '%P\n' | sort)if [ "${#TOUCHED[@]}" -eq 0 ]; then echo "No files changed" exit 0fiecho "Agent wrote ${#TOUCHED[@]} file(s)"printf ' %s\n' "${TOUCHED[@]}"
-printf '%P\n' strips the leading ./, which matters: hand git check-ignore a path with inconsistent prefixing and it will fail to match a rule that clearly applies. That produces a very confusing class of bug.
The gate itself — fail on ignored output
Of the collected paths, flag the ones an ignore rule swallows. But if you also flag every intentional temp file, the gate turns red on every run and everyone stops reading it. An allowlist keeps the signal alive.
# .agent-ignored-ok — patterns that may stay ignored (one per line)# e.g.# node_modules/# .next/# *.logALLOW_FILE="${REPO_ROOT}/.agent-ignored-ok"is_allowed() { local path="$1" [ -f "$ALLOW_FILE" ] || return 1 while IFS= read -r pat; do case "$pat" in ''|'#'*) continue ;; esac # shellcheck disable=SC2254 case "$path" in $pat*) return 0 ;; esac done < "$ALLOW_FILE" return 1}VIOLATIONS=0while IFS= read -r path; do is_allowed "$path" && continue RULE="$(git check-ignore -v "$path" 2>/dev/null || true)" echo "🛑 ignored output: $path" echo " rule: ${RULE:-(unknown)}" VIOLATIONS=$((VIOLATIONS + 1))done < <(printf '%s\n' "${TOUCHED[@]}" | git check-ignore --stdin 2>/dev/null || true)if [ "$VIOLATIONS" -gt 0 ]; then echo "🛑 ${VIOLATIONS} generated file(s) will not reach the commit. Fix .gitignore or the allowlist" exit 1fiecho "✅ No ignored output"
git check-ignore --stdin prints only the paths that matched a rule and silently drops the rest, which makes it a ready-made filter. Note that it exits 1 when nothing matched — without || true your set -e script dies on the success case. That one caught me first.
Wire it into Antigravity CLI hooks
Antigravity CLI lets you run scripts around commands. I put the gate immediately after the agent run rather than at commit time. Once you have moved on to committing, the context that would explain an empty diff is already gone, and the diagnosis gets harder.
post-agent-run is the first line of defense, pre-push the backstop for days when local hooks get bypassed. You could add the same script to CI for a third layer. So far I have not needed to.
Fix .gitignore in the right order
On day one, node_modules/ and .next/ will flood the output. If you answer by pushing everything into the allowlist, the allowlist becomes a second .gitignore and you are back where you started. Work through it in this order.
1. Exclude directories the agent should never touch, at the find stage
Dependencies and intermediate artifacts belong in find -not -path, not the allowlist. Skip this and the gate's runtime grows by an order of magnitude.
2. Remove genuinely committable output from .gitignore
Paths like public/content/ are generated and belong in the repo. Fix .gitignore itself. Routing them through the allowlist only means the next agent writing there disappears again, quietly.
3. Allowlist whatever is left
Logs, caches, lockfiles — side effects you never want committed. Narrowed this far, the allowlist across the four sites I run at Dolice sits at five to seven lines.
After roughly two weeks of use, about 10% of the red runs were byproducts that belonged in the allowlist; the rest were real .gitignore problems. If that ratio ever inverts, the allowlist is being misused.
Why this matters more for an indie developer
On a team, a reviewer notices that a pull request contains no generated files. Working alone, there is no such second pair of eyes. The wider the scope you delegate to agents, the more often failure takes this particular shape: the work succeeded, and none of it survived.
What the gate protects is not the agent's intelligence but the persistence of its output. Verification and evidence are what make agent workflows trustworthy, and this is one small, concrete instance of that principle.
Run git status --porcelain --ignored=matching | grep '^!!' | head against your own repository. Is anything in there supposed to be committed? I found an article's HTML sitting in mine.
Thank you for reading. If this saves someone half a day, it was worth writing.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.