H Heygents Docs Open App

Updated July 31, 2026 · Maintenance · High severity

Production Is Running Code That Was Never Committed to Git

How risky is it if my production server has uncommitted git changes? Very risky: it means the running app and your version history have diverged, so a redeploy, a server rebuild, or a lost SSH session can silently revert live behavior with no record of what was lost. Run git status --porcelain on the production checkout, triage the diff, commit what's safe in small batches, and move to a build-and-deploy pipeline instead of editing files in place.

Seen in 26 of 450 scanned projects (6%). Counts projects where git status --porcelain on the production checkout returned a non-trivial number of modified or untracked entries.

Common stacks: Git CI/CD PM2

How to tell you have it

  • git status --porcelain on the production checkout lists hundreds of modified or untracked files
  • A module imported by a live route exists only in the working tree, with no commit that ever added it
  • A security fix is running in production but was never pushed to any branch
  • Production is checked out on a feature branch while the default branch is stale by weeks or months
  • Entire directories on the server sit outside version control with no .gitignore entry explaining why

Why it matters

A dirty production checkout means the thing serving real traffic and the thing your git history describes are two different programs. If the server has to be rebuilt, if a disk fails, or if someone runs a routine git checkout or git pull without checking status first, uncommitted work disappears with no record it ever existed and no diff to recover it from.

Untracked modules that live routes actually import are the sharpest version of this problem: the application depends on code that git has never seen. Anyone cloning the repository to reproduce a bug, run tests, or onboard gets an app that doesn't behave like production, because a load-bearing file simply isn't there.

A security fix running unpushed in production is worse than not having fixed it in an auditable sense, because the team's record of what's actually live is wrong. If that server is later restored from a backup, redeployed, or replaced, the fix reverts silently and nobody has a diff or a commit message pointing back at why it existed.

Editing in place on a feature branch while main goes stale inverts the normal safety model: main is supposed to be the known-good source of truth, and here it's the one nobody trusts. The fix is not just committing the pending work, it's changing the workflow so production is the output of a build-and-deploy step from a branch everyone reviews, not a directory someone SSHes into and edits.

How Heygents detects it

Deep Scan runs git status --porcelain and git ls-files against the production checkout to enumerate modified, staged, and untracked files, then cross-references untracked files against what live route handlers import to flag load-bearing code with no git history. It also runs git log -1 and compares HEAD against the default branch to check for drift.

How to fix it

  1. Get an honest inventory first Before touching anything, get the full list of what's dirty and separate it into three buckets: real pending work, generated or local-only files that should be gitignored, and data files that must never be committed.
  2. Isolate data and runtime files from code Add an explicit .gitignore entry for anything that's runtime state, uploads, logs, local database files, rather than committing it or leaving it perpetually dirty. This keeps the next git status output meaningful.
  3. Commit real code changes in small, reviewable batches Group the remaining changes by feature or fix rather than one giant commit, so the history stays useful for the next person debugging a regression.
  4. Reconcile branch drift If production is running a feature branch while the default branch is stale, merge the branch into default (or vice versa) so there is one branch that matches what's actually live, and make that branch the deploy source going forward.
  5. Move to build-and-deploy Stop editing files directly on the server. Deploy by pulling a specific commit or artifact and restarting the process, so the running code is always traceable to a commit hash.

Get an honest inventory first

git status --porcelain | sort > /tmp/dirty-inventory.txt
wc -l /tmp/dirty-inventory.txt

Isolate data and runtime files from code

# .gitignore additions
data/
*.log
.env
uploads/

Commit real code changes in small, reviewable batches

git add path/to/feature-a/
git commit -m "Add feature A that was already live in production"

Reconcile branch drift

git checkout main
git merge production-branch --no-ff

Move to build-and-deploy

git pull origin main
npm ci && npm run build
pm2 restart <app>

Find this in your own projects, automatically

Heygents runs a read-only Deep Scan across every project you own, finds issues like this one, and hands you a ready-to-run fix an AI agent can execute and verify. A solo developer gets the audit, the backlog and the fix loop in one place.

Open Heygents →

Frequently asked questions

Is it ever fine to leave some files permanently uncommitted on a server?

Yes, for genuine runtime state like uploaded files, local logs, or a local .env with secrets, but those should be explicitly gitignored, not left as unexplained dirty entries. The goal is a clean git status where every remaining untracked path is intentional and documented, not a shrinking pile of exceptions.

What's the fastest way to triage hundreds of dirty files safely?

Sort by directory first and look for obvious buckets: build output, dependency folders, and data directories usually account for most of the noise and just need a .gitignore entry. What's left after removing those is almost always the actual pending code changes worth reviewing individually.

How do I commit without clobbering data that lives in the same directory as code?

Use git add on specific paths rather than git add -A or git add ., and check git status again before committing to confirm no data file slipped in. If a data directory is already gitignored, this is rarely an issue, which is another reason to fix the gitignore first.

Why is build-and-deploy safer than editing in place?

Because the deployed artifact is always derived from a specific commit, so 'what's running in production' and 'what's in git' can never diverge. Rolling back becomes a redeploy of an older commit instead of a manual, error-prone attempt to remember and undo whatever was hand-edited.