LearnNewsExamplesServices
Frontmatter
id16163
titlePre-push freshness guard trusts stale origin/dev when fetch cannot write Git metadata
stateClosed
labels
bugdeveloper-experienceaitestingbuild
assigneesneo-gpt-emmy
createdAtJul 30, 2026, 12:47 PM
updatedAtJul 30, 2026, 1:51 PM
githubUrlhttps://github.com/neomjs/neo/issues/16163
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 30, 2026, 1:51 PM

Pre-push freshness guard trusts stale origin/dev when fetch cannot write Git metadata

Closed Backlog/active-chunk-11 bugdeveloper-experienceaitestingbuild
neo-gpt-emmy
neo-gpt-emmy commented on Jul 30, 2026, 12:47 PM

Context

The pre-push branch-discipline guard tries to refresh origin/dev before it evaluates both chore-sync ancestry and the branch-freshness/revert-trap signature:

git fetch origin dev --quiet

When that fetch fails, the current implementation warns and continues against the last local origin/dev ref.

That fallback is now empirically false on restricted maintainer seats. During two pushes on 2026-07-30:

  • live GitHub dev was a75aabef808b6b132380476c3f36a528a1077553;
  • the checkout's origin/dev remained 65092deb422cec749e5817937a351b106cb27d79;
  • git fetch origin dev failed because Git could not write .git/FETCH_HEAD;
  • the hook printed using last-known local tip;
  • the remote push still completed.

The hook therefore evaluated a security/discipline gate against a reference already proven stale. The warning discloses uncertainty but does not prevent the stale input from authorizing the push.

This is a successor to #13710 / PR #13713, never a reopening. That predecessor added the mechanical revert-trap guard. Its review already identified the local-origin/dev false-negative vector; the current restricted-seat reproduction converts that design watch into a measured defect.

Live duplicate sweep: latest open queue plus issue/PR searches for pre-push FETCH_HEAD stale local tip, git fetch origin dev failed using last-known local tip, and branch freshness origin/dev fetch failed stale ref found no existing successor.

The Problem

check-branch-discipline.mjs has two outcomes that currently collapse into the same continuation:

  1. fetch failed, but the local remote-tracking ref still equals the remote branch;
  2. fetch failed, and the local remote-tracking ref is behind the remote branch.

Only the first is safe. In the second case, every downstream result is authority-invalid:

  • git log origin/dev..HEAD can miss or misclassify incoming commits;
  • the two-dot/three-dot freshness comparison can under-report the revert trap;
  • a feature push can proceed after the guard explicitly failed to acquire its source of authority.

The Architectural Reality

  • buildScripts/util/check-branch-discipline.mjs owns pre-push branch authority and already performs the refresh.
  • The consumer is Neo’s own .husky/pre-push in a maintainer checkout. This is repository-local policy, not an Agent OS orchestrator task or a hook installed into tenant repositories; dev is Neo’s integration-line authority.
  • Cloud tenant-repo-sync uses deployment-owned bare mirrors and a per-repository branchRef (default HEAD, the remote default branch). It does not invoke this hook.
  • buildScripts/util/branchFreshness.mjs owns pure, side-effect-free freshness predicates.
  • git ls-remote --exit-code origin refs/heads/dev reads the remote branch SHA without mutating the local object database or .git/FETCH_HEAD.
  • A matching remote SHA and local refs/remotes/origin/dev SHA proves that the existing local commit object is current even when the fetch command itself cannot write metadata.
  • A differing SHA proves the local diff base is stale. ls-remote cannot supply the missing commit graph, so the hook cannot truthfully continue.
  • If the remote SHA is unavailable, a push to that same remote has no authoritative pre-push basis; unavailable and current must not share an exit path.

The Fix

  1. Keep the ordinary successful-fetch path unchanged.
  2. When fetch fails, resolve the full local origin/dev SHA and the full remote dev SHA through a non-mutating remote-ref read.
  3. Continue only when both SHAs exist and are identical, with a degraded-mode warning that states the equality proof.
  4. Fail closed when either SHA is unavailable or the SHAs differ. Name the local and remote coordinates and tell the operator to refresh/rebase in an authorized Git seat before retrying.
  5. Put the equality/unavailable/mismatch decision in the existing pure branch-freshness owner and pin all branches with focused unit coverage.
  6. Add an integration witness that the pre-push script blocks on a stale local ref instead of silently evaluating the old range.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Successful git fetch origin dev Git fetch exit + updated origin/dev Run existing chore-sync and freshness checks unchanged N/A Existing owner JSDoc Existing integration suite
Fetch failure, equal local/remote SHA Local full ref + ls-remote full SHA Continue against the proven-current local object Emit degraded-mode warning Owner JSDoc Pure equality unit + integration witness
Fetch failure, differing SHA ls-remote remote dev versus local full ref Block before any ancestry/diff claim Refresh/rebase in an authorized Git seat; explicit --no-verify remains Git's operator-owned bypass Error text + owner JSDoc Pure mismatch unit + integration witness
Missing/unavailable local or remote SHA Ref-read result Block with a distinguishable unavailable reason Never reinterpret unavailable as current Error text + owner JSDoc Missing-local and remote-unavailable units

Decision Record impact

none. This repairs the authority acquisition of an existing mechanical gate; it adds no new policy or operation.

Acceptance Criteria

  • A successful fetch preserves the current pre-push behavior.
  • After a fetch failure, equal full local/remote dev SHAs allow the existing checks to run.
  • After a fetch failure, differing full SHAs block the push before chore-sync or freshness results are asserted.
  • Missing local origin/dev, unavailable remote dev, and malformed remote output each fail closed with a reason distinguishable from a stale mismatch.
  • The guarded path uses a non-mutating remote-ref read and does not write an alternate FETCH_HEAD, remote-tracking ref, or temporary object database.
  • Error output includes the exact local/remote coordinates when both are known and gives a refresh/rebase recovery path.
  • Focused pure units cover fetched, equal-degraded, mismatch, and unavailable branches.
  • A script-level witness proves the measured stale-local-ref case exits non-zero.
  • Existing chore-sync and revert-trap coverage remains green.

Out of Scope

  • Making restricted .git metadata writable.
  • Fetching objects into a second or temporary object database.
  • Changing the revert-trap threshold or converting its advisory result into a blocker.
  • Changing chore-sync detection.
  • Generalizing Neo’s checkout hook into a tenant-repository or cloud-orchestrator push policy.
  • Hiding or removing Git's explicit --no-verify bypass.

Avoided Traps

  • Do not treat a warning as authority.
  • Do not assume fetch failure means network failure; this reproduction has a reachable remote and a locally unwritable FETCH_HEAD.
  • Do not compare abbreviated SHAs.
  • Do not continue when ls-remote is unavailable; “cannot verify” is not “unchanged.”
  • Do not mutate the remote-tracking ref from the fallback path without acquiring the commit graph.

Related

Origin Session ID: 019fac4d-7844-7422-9486-7f73ccf308f5

Retrieval Hint: "pre-push fetch failed stale origin dev ls-remote fail closed FETCH_HEAD"

Authored by Emmy (GPT-5.6 Sol Ultra, Codex).

tobiu referenced in commit 8c02c4c - "fix(build): verify pre-push dev authority (#16163) (#16164) on Jul 30, 2026, 1:51 PM
tobiu closed this issue on Jul 30, 2026, 1:51 PM