Skip to content

git --cleanup=strip silently deletes every Markdown heading from composed tag messages #569

Description

@SUaDtL

The release lane composes an annotated tag message from the CHANGELOG section and writes it with git tag -a <tag> -F <message-file>. Git's default cleanup mode for -F is strip, which treats every #-prefixed line as a comment and deletes it.

Every Markdown heading in a composed tag message is therefore silently dropped: the ## [X.Y.Z] — DATE section heading and every ### Added / ### Fixed / ### Performance grouping.

This has already happened, and cannot be undone

Verified against this repository's real, published v2.8.13:

$ git tag -l -n999 v2.8.13
v2.8.13         Host-aware runtime vocabulary, plus dual-host audit-trail hardening.

    - Audit-log lines in `gate-events.log` now record the writing host
    ...

No ## [2.8.13] heading. No ### Added. The bullet bodies survived; every heading that gave them structure did not.

Under the immutable-tag doctrine (ADR-0024, SKILL.md's "Recovering from a bad release"), published tags are not retargeted or rewritten. The already-published tags stay as they are. This issue is about making future tags correct, not repairing history.

Why nothing caught it

The release skill's own guard is notes_heading_matches, which checks that the notes file's first heading matches the tag. It runs against the composed file before git tag consumes it, so it validates the input and never observes that git discarded the heading on the way in. The guard is not wrong; it is simply upstream of the loss.

Found by the consumer-portability lane driver (#563), which executes the skill's invocation strings as the prose literally spells them rather than calling the library directly. A direct-import test could not have seen this: the defect lives in the gap between what the prose says to run and what that command actually does.

Options

  1. --cleanup=verbatim — preserves the message exactly. Simplest and most faithful to "the tag carries the changelog section". Also preserves trailing whitespace and blank lines, which is a cosmetic cost.
  2. --cleanup=whitespace — strips leading/trailing blank lines and trailing whitespace, but keeps # lines. Probably the right balance.
  3. Reformat the composed message to avoid # at line start (e.g. underline-style headings). Rejected on sight: it makes the tag message diverge from the CHANGELOG section it is supposed to reproduce verbatim, which is the property the release skill explicitly requires.

Option 2 looks correct, but the choice belongs to whoever owns the release surface.

Also worth deciding

Whether a guard should assert the property after the fact — read the tag back with git tag -l -n999 or git cat-file tag and confirm the message still contains its heading. The skill already has a read-back discipline for the published GitHub Release ("verify publication, never assume it") on exactly the reasoning that a step can partially succeed. This is the same shape one layer down.

Acceptance

  • Composed tag messages retain their ## and ### headings
  • A test asserts the heading survives the round trip through a real git tag -a -F, not just in the composed file
  • The choice of cleanup mode is recorded with its reasoning
  • Already-published tags are left untouched

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsev:medTribunal/triage: medium severity

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions