Skip to content

guards(git): reviving a stale branch can silently reverse a later fix — 75 of 87 revivable branches carry one #17178

Description

@mrveiss

Problem

A stale branch does not merely lag main — it can carry a reversal of a fix that landed after
it. Reviving one, or resolving its conflicts in its own favour, silently removes the later fix.

This was found while triaging the local branch backlog, and it is not rare.

Verified case

issue-15165-search-recursion is the ideal-looking revival candidate: 6 files touched, 3 unlanded,
focused commits that name their issue. Its entire residual diff in
autobot-backend/knowledge/search.py is one hunk replacing

logger.warning("ChromaDB filtered query failed (%s); no results, never an unfiltered retry (#16662)", exc)
return {"ids": [[]], "documents": [[]], "metadatas": [[]], "distances": [[]]}

with a fallback that does kwargs.pop("where", None) and re-queries unfiltered. That reverses
#16662: the where filter is what scopes results to the permitted set, so the retry returns
documents the caller is not entitled to see. The branch predates #16662 and never learned about it.

The recursion fix those commits advertise had already landed on main by another path, so the
regression is the only residue. Two further branches (issue-15455-ratchet-2b,
train/2026-09-13-batch2b) carry the same stale hunk. Recorded on #15165.

Scale

Of 118 local branches carrying genuinely unlanded work, 87 are tightly scoped (≤8 unlanded files)
and therefore look revivable. 75 of those 87 hold at least one file predating a later fix. 12 are
clean.

A hit means the branch's copy of a file predates a later fix — a revival hazard, not a proven
regression. A clean rebase keeps main's line. It bites when the branch's version is taken
wholesale or a conflict is resolved toward the branch, which is exactly what "update the branch and
continue" invites. Small and focused is what makes a branch dangerous; nobody merges an obviously
dead 2,000-file branch.

Why the existing guards miss it

Every structural test calls these branches unlanded work: commits are unreachable from main, the
diff is non-empty, the commit subjects cite a real open issue. Only the content distinguishes
"work that never landed" from "a fix that was superseded and would now regress". Nothing currently
looks at that.

Acceptance criteria

  • A guard that, given a branch, reports any file whose content predates a fix main already
    carries, and names the later issue — runnable before a branch is revived or rebased
  • The detector's two mandatory filters are implemented and tested: issue references capped at
    the repository's real maximum issue number (#800000 and #336791 are CSS hex colours, and
    an uncapped scan flags ~95% of branches), and hub files excluded (ratchet baselines,
    known-large lists, .secrets.baseline, .github/) since every stale branch churns those
  • The guard distinguishes nothing found from did not look in its output
  • Documented rule in the git/branch developer docs: never git checkout <branch> -- <file>;
    resolve conflicts toward main unless the branch's version adds something stateable
  • The 75 flagged branches are each dispositioned with evidence, or the list is regenerated and
    re-triaged if the backlog has moved

Related

Activity

  1. mrveiss commented on Sep 20, 2026

    @mrveiss
    OwnerAuthor

    The detector described above is a floor on the hazard, not a measure of it

    Two further regressions were found today on lane-gui-dedup (#14907/#14908) that the proposed
    scan cannot see. Recording them here so the guard is not built to the wrong specification.

    1. A file that exists only on the branch. autobot-frontend/src/services/RedisServiceAPI.ts
    is present on the branch and absent from main — deliberately removed by fa76c273cd /
    4b6c4e4c24 ("remove the unreachable user-GUI Redis client", #16245/#16252). Reviving the branch
    resurrects dead code a landed cleanup removed. Because main has no such file, there is no removed
    line citing a later issue, so a diff-line scan reports nothing.

    2. A deletion by omission. The branch's autobot-slm-frontend/src/composables/usePrometheusMetrics.ts
    lacks meanOfKnown(), summariseGpus() and the GPUNodeStatus import — GPU-utilisation
    aggregation that main added later under #15226 ("an unmeasured fleet is unknown, not a fleet
    sitting at 0%"). The branch's copy is simply older. There is no marker of any kind to match on.

    Both were found by a person reading the diff, not by any scan.

    Consequence for the acceptance criteria

    A clean result from the line-based detector must be reported as "nothing detected", never as
    "safe" — this is the nothing found versus did not look distinction the criteria already require,
    and these two cases are precisely where it bites.

    Two criteria to add:

    • The guard reports files present on the branch but absent from main, with the commit that
      removed each — a deletion on main is as much a later decision as an edit
    • The guard reports symbols defined on main but missing from the branch's copy of the same
      file, which is how a later feature is silently dropped when the branch's version is taken

    Until those exist, the line scan's output is a starting point for reading a diff, not a substitute
    for it.

  2. added this to the v0.9.5 - CI and guards milestone on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions