#5138·gbrain

brain-commit-push.sh reports "PUSH FAILED / NEEDS ATTENTION" on pushes that succeeded

Author: vanbradyCreated Sep 16, 2026Updated Sep 17, 2026

Summary

The generated brain_push() helper treats a rejected push as failure without asking the remote whether the commit actually landed. When a concurrent pusher wins the race, the remote rejects our push for a stale expected ref — and the rejection message names our own commit as the ref's current value, i.e. the work is safe. The helper then tries git pull --rebase, which aborts on a dirty tree, and exits 4 with:

PUSH FAILED — commit is local-only, NEEDS ATTENTION

This happened on five consecutive commits in one session. All five were on origin.

The concurrent pusher is gbrain itself: a gbrain serve process watching the checkout pushes the moment a commit lands, so this is self-inflicted and probably common for anyone running serve against a source repo.

Evidence

From ~/.gbrain/brain-push.log, one commit:

2026-09-16T04:34:32Z [push] ok master 7ddb4bf
To https://github.com/<user>/<repo>.git
 ! [remote rejected] HEAD -> master (cannot lock ref 'refs/heads/master':
   is at 7ddb4bf46... but expected 23444350...)
error: failed to push some refs
2026-09-16T04:34:33Z [push] rejected; rebase-pull master
error: cannot pull with rebase: You have unstaged changes.
2026-09-16T04:34:33Z [push] LOCAL-ONLY, NEEDS ATTENTION: master @ 7ddb4bf could not reach origin

7ddb4bf is the commit being pushed. The remote reports it as the ref's current value in the same line that declares failure.

Why it matters

The helper's own header states the contract:

THE DURABILITY GUARANTEE: add -> commit -> push, atomically. Refuses to exit 0 without a confirmed push

Exiting 4 on a confirmed push inverts that. Under cron it trains the operator — and any agent reading the log — to ignore the one alarm that is supposed to be unignorable.

Two contributing causes

  1. A rejected push is not proof of failure. Nothing verifies against the remote.
  2. The recovery path cannot run on a dirty tree. git pull --rebase aborts with "You have unstaged changes", which is the normal unattended state. Notably, bin/x-ingest.sh in my own repo already documents this exact trap and uses --autostash for it.

Suggested fix

After a failed push, ask origin before recovering:

bash
if git fetch --quiet origin "$_branch" &&
   git merge-base --is-ancestor HEAD "origin/$_branch"; then
  echo "... [push] ok-already-on-remote $_branch $(git rev-parse --short HEAD)" >>"$_log"
  return 0
fi

…and add --autostash to the recovery pull, plus the same ancestor check once more before declaring LOCAL-ONLY.

I've been running exactly this locally for a day: commits that previously reported NEEDS ATTENTION now log ok-already-on-remote and exit 0, and a genuine unreachable-origin failure still exits 4. Happy to open a PR.

One request either way

The function is marked --- gbrain durability push-retry (generated; one source of truth) ---, and gbrain sources harden regenerates it, silently reverting local fixes. A documented way to keep an operator patch across regeneration — or a checksum warning when one is overwritten — would help regardless of whether this bug is fixed.

Environment

gbrain 0.50.5.0 (install_method: binary) · macOS 26 (Darwin 25.6.0, arm64) · git 2.x · remote GitHub over HTTPS