bug(prep): the [Unreleased] drop refusal prints a CRLF file's \r, so a terminal overwrites the line it names #1507

Closed
opened 2026-10-02 12:28:50 +02:00 by claude · 1 comment
Member

rt prep refuses [Unreleased] text it would drop (prep.UnreleasedDropError, from #1486), naming each line. The refusal prints each UncarriedLine.Text verbatim. On a CRLF CHANGELOG.md, every record keeps its trailing \r, and that \r reaches the terminal.

Measured

This was a Go probe on main @ 2060ed1, 2026-10-02. It made the same calls rt prep makes: parser.UnreleasedContent(clBytes) (cmd/rt/prep.go:195), changelog.UnreleasedStartLine(clBytes) (:298), then prep.Compose. The CHANGELOG used CRLF line endings, with this [Unreleased]:

Prose.

### Notes

- n

Compose returns *prep.UnreleasedDropError. Detection is correct, with the same lines as on LF input. The refusal's second line is:

"  CHANGELOG.md:5: Prose.\r    <- before the first ### heading"

In a terminal, the \r returns the cursor to column 0. The reason after it is then drawn over the start of the line. Here the reason is the longer part, so CHANGELOG.md:5: Prose. is no longer visible.

Why it matters

The refusal exists to point the operator at the exact lines to fix (#1486). #1485 made this repo check out LF, but an adopter's CHANGELOG.md can still be CRLF. There, the refusal shows reasons without the lines they belong to.

Acceptance criteria

  • On a CRLF CHANGELOG, rt prep's refusal contains no raw \r, and it names the same file lines as on LF input. — DONE @ #1508 (de49186, reviewed as de49186). TestCompose_refusalOnACRLFChangelog reads the CHANGELOG with the two calls rt prep makes and asserts that the CRLF refusal is byte-equal to the LF one, naming lines 5, 7 and 9.
  • UncarriedLine.Text stays verbatim; only the rendering strips the \r. — DONE @ #1508 (de49186, reviewed as de49186). The new UncarriedLine.Render strips it at print time, and both rt prep and the fold-back refusal use it. The test checks that each CRLF Text keeps its \r.
  • LF output stays byte-identical. Its tail is already pinned in internal/prep/unreleased_drop_test.go. — DONE @ #1508 (de49186, reviewed as de49186). The new test pins the whole LF refusal and passes against 2060ed1's code. The literal tail pin is unchanged.
  • Tests are red on main and mutation-verified (AGENTS.md section 8, Axis B). — DONE @ #1508 (de49186, reviewed as de49186). The test is red against 2060ed1's code at the raw-\r and CRLF-vs-LF assertions. Mutants: 6/6 killed at 5682563. At de49186, the red-on-main check, M1, M5, M7 and an added-kind probe were re-run, and review 7169 reproduced them.

Notes

  • #1493 solved the same problem in changelog.FoldBackDropError.Describe, with an unexported unCR in internal/changelog/foldback.go. One exported rendering helper used by both refusals would keep them from drifting. #1493 did the same for the accepted-headings note and the remedy (changelog.AcceptedHeadingsNote, changelog.UncarriedRemedy).
  • Raised in the review of #1493 (review 7158 finding 2; re-review 7160).
`rt prep` refuses `[Unreleased]` text it would drop (`prep.UnreleasedDropError`, from #1486), naming each line. The refusal prints each `UncarriedLine.Text` verbatim. On a CRLF `CHANGELOG.md`, every record keeps its trailing `\r`, and that `\r` reaches the terminal. ## Measured This was a Go probe on `main` @ `2060ed1`, 2026-10-02. It made the same calls `rt prep` makes: `parser.UnreleasedContent(clBytes)` (`cmd/rt/prep.go:195`), `changelog.UnreleasedStartLine(clBytes)` (`:298`), then `prep.Compose`. The CHANGELOG used CRLF line endings, with this `[Unreleased]`: ``` Prose. ### Notes - n ``` `Compose` returns `*prep.UnreleasedDropError`. Detection is correct, with the same lines as on LF input. The refusal's second line is: ``` " CHANGELOG.md:5: Prose.\r <- before the first ### heading" ``` In a terminal, the `\r` returns the cursor to column 0. The reason after it is then drawn over the start of the line. Here the reason is the longer part, so `CHANGELOG.md:5: Prose.` is no longer visible. ## Why it matters The refusal exists to point the operator at the exact lines to fix (#1486). #1485 made this repo check out LF, but an adopter's `CHANGELOG.md` can still be CRLF. There, the refusal shows reasons without the lines they belong to. ## Acceptance criteria - [x] On a CRLF CHANGELOG, `rt prep`'s refusal contains no raw `\r`, and it names the same file lines as on LF input. — DONE @ #1508 (`de49186`, reviewed as `de49186`). `TestCompose_refusalOnACRLFChangelog` reads the CHANGELOG with the two calls `rt prep` makes and asserts that the CRLF refusal is byte-equal to the LF one, naming lines 5, 7 and 9. - [x] `UncarriedLine.Text` stays verbatim; only the rendering strips the `\r`. — DONE @ #1508 (`de49186`, reviewed as `de49186`). The new `UncarriedLine.Render` strips it at print time, and both `rt prep` and the fold-back refusal use it. The test checks that each CRLF `Text` keeps its `\r`. - [x] LF output stays byte-identical. Its tail is already pinned in `internal/prep/unreleased_drop_test.go`. — DONE @ #1508 (`de49186`, reviewed as `de49186`). The new test pins the whole LF refusal and passes against `2060ed1`'s code. The literal tail pin is unchanged. - [x] Tests are red on `main` and mutation-verified (AGENTS.md section 8, Axis B). — DONE @ #1508 (`de49186`, reviewed as `de49186`). The test is red against `2060ed1`'s code at the raw-`\r` and CRLF-vs-LF assertions. Mutants: 6/6 killed at `5682563`. At `de49186`, the red-on-main check, M1, M5, M7 and an added-kind probe were re-run, and review 7169 reproduced them. ## Notes - #1493 solved the same problem in `changelog.FoldBackDropError.Describe`, with an unexported `unCR` in `internal/changelog/foldback.go`. One exported rendering helper used by both refusals would keep them from drifting. #1493 did the same for the accepted-headings note and the remedy (`changelog.AcceptedHeadingsNote`, `changelog.UncarriedRemedy`). - Raised in the review of #1493 (review 7158 finding 2; re-review 7160).
Author
Member

Landed on main as de4918684856ff3ef364de2381866e5e7576bd42 via #1508 (rebase merge, 2026-10-02 13:23:04).

  • Landing vs review. It was a fast-forward: 5682563 and de49186 landed with their reviewed SHAs. git range-diff 2060ed1..de49186 2060ed1..origin/main shows = for both, and the trees match.
  • Approval. alex, review 7171: APPROVED, official, bound to de49186, the merged head. It was submitted at 13:08:48, after the HOLD for the review-7168 nit was released (HOLD comment 113946 at 12:52:20, released by 113949 at 12:57:37), so it follows the AGENTS.md section 6.2 order.
  • Other reviews. Claude reviews 7168 (at 5682563) and 7169 (at de49186) were posted as COMMENT, because that account authored the PR. Both give the verdict APPROVE, and 7169 re-read the head after the HOLD was released.
  • Checks. PR CI on de49186: 30/30 success, read right before the merge. push:main on de49186, at the time of writing: 14 success, 1 failure, and 1 pending (release / fire-cut, waiting to run). The failure is go-ci / record reviewed vs landed commit (push). Its log (run 18842, job 2) reads comment page 2 repeated the previous non-empty page; endpoint did not advance; refusing to grade, which is the recorder defect in #1495, not this change. check-self-bootstrap (push) is green, as the PR's re-pin section predicted.
  • Rolling PR. The push:main prep regenerated #1494 at the landed head, and its body now carries this change's fragment line. So rt prep picked up the change from main with no re-pin.
  • Closing references. Before the merge, the PR title, body, both commit messages, both comments and all three review bodies were scanned with the AC-closure gate's regex: no closing keyword. The CI ac-closure-check context on de49186 passed.
  • Branch. claude/1507-prep-refusal-crlf was deleted after the range-diff check.

All four acceptance criteria are ticked above (DONE @ #1508). Closing by hand.

Landed on `main` as `de4918684856ff3ef364de2381866e5e7576bd42` via #1508 (rebase merge, 2026-10-02 13:23:04). - **Landing vs review.** It was a fast-forward: `5682563` and `de49186` landed with their reviewed SHAs. `git range-diff 2060ed1..de49186 2060ed1..origin/main` shows `=` for both, and the trees match. - **Approval.** alex, review 7171: APPROVED, official, bound to `de49186`, the merged head. It was submitted at 13:08:48, after the HOLD for the review-7168 nit was released (HOLD comment 113946 at 12:52:20, released by 113949 at 12:57:37), so it follows the AGENTS.md section 6.2 order. - **Other reviews.** Claude reviews 7168 (at `5682563`) and 7169 (at `de49186`) were posted as COMMENT, because that account authored the PR. Both give the verdict APPROVE, and 7169 re-read the head after the HOLD was released. - **Checks.** PR CI on `de49186`: 30/30 success, read right before the merge. push:main on `de49186`, at the time of writing: 14 success, 1 failure, and 1 pending (`release / fire-cut`, waiting to run). The failure is `go-ci / record reviewed vs landed commit (push)`. Its log (run 18842, job 2) reads `comment page 2 repeated the previous non-empty page; endpoint did not advance; refusing to grade`, which is the recorder defect in #1495, not this change. `check-self-bootstrap (push)` is green, as the PR's re-pin section predicted. - **Rolling PR.** The push:main prep regenerated #1494 at the landed head, and its body now carries this change's fragment line. So `rt prep` picked up the change from `main` with no re-pin. - **Closing references.** Before the merge, the PR title, body, both commit messages, both comments and all three review bodies were scanned with the AC-closure gate's regex: no closing keyword. The CI `ac-closure-check` context on `de49186` passed. - **Branch.** `claude/1507-prep-refusal-crlf` was deleted after the range-diff check. All four acceptance criteria are ticked above (DONE @ #1508). Closing by hand.
Sign in to join this conversation.
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
frankenbit/release-toolkit#1507
No description provided.