Warn that submodule pins go backwards silently on merge #25

Merged
geusebio merged 1 commit from docs/submodule-pin-regression-on-merge into main 2026-08-15 16:29:37 +00:00
Owner

A long-lived branch drags every submodule pin back to where it forked, and the merge does not conflict. Nothing on the branch touched the gitlink, so from git's point of view only one side moved — and a 3-way merge takes the side that moved. That side is the branch's, which is older.

Nothing in VCS.md warned about this. The only prior mention of submodules was the [skip ci] classification bullet ("a submodule bump is code"), which answers a different question entirely.

What prompted it

A merge of a long-lived feature branch into Grey.ooo/Someones.Computer reverted four gitlinks at once, bundled in with an unrelated test-coverage change:

Submodule Was Became
.claude/agent-standards f917d8d fc64628
agent 473efd1 24853cb
scclient 9d955d9 e905eea
tray ee8b776 1f5b885

For this repo specifically, that un-shipped #22 (tea CI-status parsing pitfalls and idle-friendly loop polling) and #23 (the concise-recap Stop hook) for every consumer, and nobody noticed for hours. It survived review because a gitlink renders as a single opaque -Subproject commit / +Subproject commit pair — no dates, no subjects, no ordering — so a reviewer reading a PR about coverage has no reason to resolve those hashes.

It was eventually caught only because a different PR happened to be resolving submodule conflicts in the same area and noticed the merge base disagreeing with main.

What this adds

A ### Submodule pins go backwards silently section under Branching, covering:

  • The check, before committing a merge rather than after it landsgit diff --name-only <base> HEAD -- <submodule path>.
  • The test that distinguishes a bump from a regressiongit -C <submodule> merge-base --is-ancestor <old> <new>. Eyeballing two hashes cannot tell you which is newer; this can.
  • Three outcomes, not two — forward (fine), backwards (restore the descendant; fixing a pin your own merge regressed is finishing that merge, not a second change), and diverged, which is a real decision about which tree ships and belongs to the author rather than whoever is merging.
  • Two corollaries the same incident argued for: resolving a submodule conflict with --ours/--theirs is almost always wrong, since each silently reverts one side; and a pin move never belongs bundled with an unrelated change, because it is invisible in review and unattributable afterwards.

Cross-referenced from the before-you-commit checklist and the quick reference, so it is reachable from the two places someone is actually looking when it matters.

Verification

Docs-only — one file, docs/Topics/VCS.md, 52 insertions, no deletions. This repo ships no markdownlint config and no CI workflows, so there is no automated gate to run; checked by hand instead: both new code fences carry a bash language, the new #submodule-pins-go-backwards-silently anchor matches its heading exactly, and every new table row has balanced delimiters.

Opened ready rather than draft, per Pure-docs PRs skip the draft.

The consuming repo's own regression is being fixed separately in Someones.Computer#680, which moves .claude/agent-standards forward to this repo's current tip. That PR carries the pointer bump; this one carries the prose — the two-PR split this file already prescribes.

**A long-lived branch drags every submodule pin back to where it forked, and the merge does not conflict.** Nothing on the branch touched the gitlink, so from git's point of view only one side moved — and a 3-way merge takes the side that moved. That side is the branch's, which is older. Nothing in `VCS.md` warned about this. The only prior mention of submodules was the `[skip ci]` classification bullet (*"a submodule bump is code"*), which answers a different question entirely. ## What prompted it A merge of a long-lived feature branch into `Grey.ooo/Someones.Computer` reverted **four gitlinks at once**, bundled in with an unrelated test-coverage change: | Submodule | Was | Became | |---|---|---| | `.claude/agent-standards` | `f917d8d` | `fc64628` | | `agent` | `473efd1` | `24853cb` | | `scclient` | `9d955d9` | `e905eea` | | `tray` | `ee8b776` | `1f5b885` | For this repo specifically, that un-shipped **#22** (tea CI-status parsing pitfalls and idle-friendly loop polling) and **#23** (the concise-recap Stop hook) for every consumer, and nobody noticed for hours. It survived review because a gitlink renders as a single opaque `-Subproject commit` / `+Subproject commit` pair — no dates, no subjects, no ordering — so a reviewer reading a PR about coverage has no reason to resolve those hashes. It was eventually caught only because a *different* PR happened to be resolving submodule conflicts in the same area and noticed the merge base disagreeing with `main`. ## What this adds A `### Submodule pins go backwards silently` section under **Branching**, covering: - **The check, before committing a merge rather than after it lands** — `git diff --name-only <base> HEAD -- <submodule path>`. - **The test that distinguishes a bump from a regression** — `git -C <submodule> merge-base --is-ancestor <old> <new>`. Eyeballing two hashes cannot tell you which is newer; this can. - **Three outcomes, not two** — forward (fine), backwards (restore the descendant; fixing a pin your own merge regressed is finishing that merge, not a second change), and **diverged**, which is a real decision about which tree ships and belongs to the author rather than whoever is merging. - **Two corollaries the same incident argued for:** resolving a submodule conflict with `--ours`/`--theirs` is almost always wrong, since each silently reverts one side; and a pin move never belongs bundled with an unrelated change, because it is invisible in review and unattributable afterwards. Cross-referenced from the before-you-commit checklist and the quick reference, so it is reachable from the two places someone is actually looking when it matters. ## Verification Docs-only — one file, `docs/Topics/VCS.md`, 52 insertions, no deletions. This repo ships no markdownlint config and no CI workflows, so there is no automated gate to run; checked by hand instead: both new code fences carry a `bash` language, the new `#submodule-pins-go-backwards-silently` anchor matches its heading exactly, and every new table row has balanced delimiters. Opened ready rather than draft, per *Pure-docs PRs skip the draft*. ## Related The consuming repo's own regression is being fixed separately in [Someones.Computer#680](https://git.grey.ooo/Grey.ooo/Someones.Computer/pulls/680), which moves `.claude/agent-standards` forward to this repo's current tip. That PR carries the pointer bump; this one carries the prose — the two-PR split this file already prescribes.
A long-lived branch drags every gitlink back to where it forked, and the merge
does not conflict: nothing on the branch touched the pin, so only one side
moved and a 3-way merge takes that side — the older one.

This actually happened. One merge of a long-lived feature branch into
Someones.Computer reverted four gitlinks at once (`agent`, `scclient`, `tray`
and `.claude/agent-standards`), bundled in with an unrelated coverage change.
It un-shipped three standards commits — including #22 and #23 — for every repo
consuming them, and survived review because a gitlink renders as one opaque
`-Subproject commit` / `+Subproject commit` pair with no dates, subjects or
ordering to read.

Nothing in this file warned about it. The only prior mention of submodules was
the `[skip ci]` classification bullet, which is about a different question.

Adds a section under Branching with the before-commit check, the
`merge-base --is-ancestor` test that distinguishes a forward bump from a
regression, and the three outcomes (forward / backwards / diverged — the last
being the author's decision, not the merger's). Plus two corollaries the same
incident argued for: resolving a submodule conflict by picking a side is
almost always wrong, and a pin move never belongs bundled with an unrelated
change.

Cross-referenced from the before-you-commit checklist and the quick reference.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Grey.ooo/agent-standards!25
No description provided.