The scariest thing in the system I use to auto-update four technical blogs every day was never the red error message — it was the green log. The scheduled run reported git push success, yet no new articles appeared on the site. Locally the push seemed to run fine, but nothing reached the remote. Tracking down this silent failure cost me a few days, mostly because I was chasing the wrong cause.
Here is the uncomfortable truth: git push is a command that succeeds even when it does nothing. If you build automation with Claude Code or inside a sandbox, that property turns into a quiet trap. Because no error appears, it is easy to miss. Let me walk through the silent commit failure behind it.
Symptom — no error at all, yet the changes vanish
A typical log looks like this:
$ git add content/
$ git commit -m "Add: new article (JA+EN)"
$ git push origin main
Everything up-to-date
git push returns Everything up-to-date with exit code 0, so the script happily moves on. But on GitHub there is no new commit. CI never fires, no deploy happens. Even if you guard with if [ $? -ne 0 ], the push itself is treated as a success, so it never trips.
The problem is not the push. The commit right before it failed without making a sound.
Why it happens — two ways commit gets silently skipped
Pattern 1: git identity is unset on a fresh clone
This is by far the most common cause. Right after git clone in a CI container or sandbox, user.name and user.email are not configured. Running git commit in that state can exit without creating a commit:
Author identity unknown
*** Please tell me who you are.
The trap is that if you pipe output away or never check commit's exit code, this warning stays invisible and you sail straight into git push. Since no commit was created, your local HEAD still matches the remote — so push naturally reports Everything up-to-date.
For a long stretch my scheduled task did not re-set identity right after git clone --depth 1, and I simply never noticed.
Pattern 2: nothing was actually staged
You may think you ran git add, but the path was off and nothing got staged, or the generated files are excluded by .gitignore. Then git commit ends with:
On branch main
nothing to commit, working tree clean
Again no commit is created, and the exit code is 1 (or 0 depending on the environment). The following push is once more Everything up-to-date. This is exactly what happens when your generated files never landed in the directory you expected.
Pattern 3: you committed and pushed to different branches
A surprisingly easy one to miss: the commit succeeded, but the branch you pushed is not the branch you committed to. A shallow --depth 1 clone of a specific commit can leave you on a detached HEAD, and committing there stacks the commit on a nameless branch. Or the repository's default branch is master while your script runs git push origin main — nothing is on main, so Everything up-to-date is the honest answer.
# check which branch you actually committed to
git rev-parse --abbrev-ref HEAD # prints "HEAD" if detached
git branch --show-current # empty if detached HEADPassing --branch main to the clone and confirming the current branch right before pushing prevents this mix-up.
Reproduce it
Watching the behavior locally makes the fix obvious. Clear the identity, try to commit, and you can reproduce the empty push:
# throwaway verification clone
git clone --depth 1 https://github.com/you/your-repo.git /tmp/verify
cd /tmp/verify
# deliberately unset identity
git config --unset user.name 2>/dev/null
git config --unset user.email 2>/dev/null
echo "test" >> README.md
git add README.md
git commit -m "test commit" # fails here with identity unknown
echo "commit exit code: $?" # prints a non-zero code like 128
git push origin main # still Everything up-to-dateYou will see that commit exit code is non-zero, and yet the push is treated as a success.
The fix — separate "configure" from "verify"
Handle this in two layers: a "configure" step to prevent it, and a "verify" step to catch it when it slips through anyway.
1. Set identity right after every clone
In automation, set identity unconditionally on every clone. It persists for that repository once set, but a re-clone wipes it, so just set it each time:
git clone --depth 1 --branch main "https://${TOKEN}@github.com/you/your-repo.git" "$WORK"
cd "$WORK"
# identity is empty right after clone — set it unconditionally
git config user.email "you@example.com"
git config user.name "your-name"2. Judge push success by SHA comparison
This is the most important part. The exit code of git push cannot be trusted, so decide success by whether your local HEAD now matches the remote HEAD. That single check catches both the silent commit failure and a missed push.
# check the commit's exit code too
git add content/
if ! git commit -m "Add: new article (JA+EN)"; then
echo "❌ commit failed (no changes, or identity unset)"
exit 1
fi
# after push, compare local and remote SHAs
git push origin main
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git ls-remote origin -h refs/heads/main | cut -f1)
if [ "$LOCAL" = "$REMOTE" ]; then
echo "✅ push confirmed: $LOCAL is live on the remote"
else
echo "❌ mismatch — local=$LOCAL remote=$REMOTE. The push did not land"
exit 1
figit ls-remote queries the remote's current SHA directly, so it is not fooled by a stale local cache. Once I adopted the rule that a push counts as successful only after this comparison passes, the "success log but no new article" problem disappeared from my auto-update pipeline.
In automation, make "never trust the exit code" the default
What this taught me is that in automation an exit code is only a starting point. Commands that "succeed even when they do nothing," like git push, are not rare. Confirm that the artifact actually exists — that a commit was stacked, that the file arrived — through the state itself, not the return value. I now bring the same posture to API calls and file generation, not just Git.
What makes a silent failure so stubborn is that no matter how many times you reread the log, it only ever says "fine." An error gives you text to search for; a success that did nothing leaves you nothing to grab onto. As an indie developer running automation solo, no one else is around to raise a flag. I lost days convinced I had misread the log and went looking in entirely the wrong place. That is why I now treat one extra step — mechanically asking whether the artifact really landed — as the default behavior of the pipeline rather than an afterthought.
When you hit other Git walls, it is worth checking the Claude Code git index.lock fix for VM and CI and resolving git dubious ownership with safe.directory. And when the git CLI simply will not stabilize in an environment, switching to committing and pushing via the GitHub REST API is a realistic option.
Start by adding those three lines of SHA comparison right after git push in your automation script. The next time a "success log" prints, you will finally be able to say, with confidence, whether it is real.