---
key: ci.decisions-edit-trailer
title: 2026-07-25 — The rationale-edit marker is a git trailer, not a substring anywhere in the commit range (#609)
status: active
since: '2026-07-25'
supersedes: none
superseded-by: none
rule: The body-diff exemption is armed by an affirmative `Decisions-Edit:` **git trailer** (`yes`/`true`/`1`, case-insensitive, read with `unfold`) on some NON-MERGE commit in the PR's merge-base range — never by a substring search over the message text. A non-affirmative value (`no`) does not arm it, the retired `[decisions-edit]` substring arms nothing (the validator emits a `::warning::` nudge when it sees one without a trailer), and a git error leaves the guard ON.
signals: 'decisions-edit, Decisions-Edit trailer, rationale-prose edit, body-diff guard, decisions_validate, vacuous gate, green no-op · paths: `scripts/decisions_validate.py`, `scripts/tests/test_decisions_validate.py` · issues: #609, #603, #521'
mechanics: '`git log --no-merges --format=''%(trailers:key=Decisions-Edit,valueonly,unfold)'' <mb>..<head>`; `dv._edit_trailer_armed`'
---

**The guard could disable itself by being described.** The original check was
`EDIT_TOKEN.lower() in git log --format=%B mb..head`, so *any* commit message containing the literal
string anywhere set `token = True` and suppressed all three rewrite comparisons (active survivors,
active→archive laundering, archive survivors). It fired live in PR #605: a commit message explaining
*why no token was needed* contained the bracketed token, and armed it. The result was a **green
`--base/--head` run that was vacuous on the body-diff dimension**, in the one PR that hand-resolved a
merge conflict inside the corpus the guard exists to police. No content was damaged, but the gate
reported success while checking nothing — the "a gate can merge green yet be a no-op" class.

**Why a trailer and not an own-line match.** Requiring the token alone on its own line fixes the
observed instance, but not the class: this repo's docs commits routinely *quote example commit
messages*, and the commit introducing this very record does so. An own-line matcher would arm on the
example. Git recognises a trailer only in the message's final paragraph, so a `Decisions-Edit: yes`
line quoted mid-body followed by more prose parses as nothing at all (verified against git 2.55).
Requiring an *affirmative value* closes the same trap one level up: `Decisions-Edit: no`, the natural
way to record a deliberate non-edit, must not read as consent.

**Three exactness requirements the naive trailer read still gets wrong** (all found in review, all
regression-tested). `--no-merges`: on a `pull_request` event `actions/checkout` lands on a synthetic
merge commit whose body the forge composes from the PR *description*, so without it a description
ending in an example marker arms a guard no author armed. That exclusion is not free: merging main
into a PR branch is discouraged but not mechanically blocked (`prepush-rebase-check.sh` only refuses a
branch that is *behind* main, and a merge makes main an ancestor), so an author who marks ONLY such a
merge commit has a legitimate rewrite rejected — a LOUD failure costing one extra commit, deliberately
preferred over silently disabling the guard. `unfold`: a folded `Decisions-Edit: no` + continuation
` yes` otherwise yields two lines, and the continuation arms on its own — inverting the value the
author wrote. And resolving the marker **fails closed** on a git error, the one deliberate exception
to this module's fail-open posture: a fail-open marker lookup is #609 through a different door.

**The residual, stated rather than papered over:** a quoted example that is the *final* paragraph of
an ordinary commit message is, by git's own grammar, a trailer — and will arm. That is inherent to any
trailer scheme; what the change buys is that merely *mentioning* or *discussing* the marker, which is
what actually happened in #605, can no longer disable anything.

**The marker's scope is unchanged** — still narrow, still only for a rationale-prose edit to a
surviving or archived record (`docs.decision-lifecycle`); only its form changed. It coexists with the
`Co-Authored-By:` trailer the `commit-msg` hook already mandates, so armed commits carry a
multi-trailer block. The retired form is left detectable on purpose: a contributor with the old habit
gets a warning naming the form change, not a bare "prose changed" failure.
