Skip to content

Add a trailing-newline gate over changelog.d and the pinned data READMEs: the defect has now shipped in three PRs and no gate anywhere in the repo checks for it - #368

Closed
jaylfc wants to merge 1 commit into
masterfrom
exec/tsk-7m6ju5
Closed

Conversation

@jaylfc

@jaylfc jaylfc commented Aug 18, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): Add a trailing-newline gate over changelog.d and the pinned data READMEs: the defect has now shipped in three PRs and no gate anywhere in the repo checks for it

Autonomous build of board card tsk-7m6ju5.

Files:
changelog.d/tsk-7m6ju5-trailing-newline-gate.md | 2 +
scripts/check_trailing_newline.py | 116 ++++++++++++++++
tests/test_trailing_newline_gate.py | 170 ++++++++++++++++++++++++
3 files changed, 288 insertions(+)

scripts/check_trailing_newline.py new gate checking changelog.d md files and benchmarks/data/README.md for exactly one trailing 0x0a
tests/test_trailing_newline_gate.py six fixture cases plus four variants
changelog.d/tsk-7m6ju5-trailing-newline-gate.md added
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jaylfc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d985b1d7-28e8-4869-806c-5bbe653c5513

📥 Commits

Reviewing files that changed from the base of the PR and between dad7c0e and d7fb567.

📒 Files selected for processing (3)
  • changelog.d/tsk-7m6ju5-trailing-newline-gate.md
  • scripts/check_trailing_newline.py
  • tests/test_trailing_newline_gate.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

)


def _last_byte_hex(file_path: Path) -> str | None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING]: _last_byte_hex is defined but never called

This function is dead code. It computes a hex representation of the last byte but is never invoked anywhere in the module. Remove it to avoid confusion for future maintainers.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

``pass`` is ``False`` (``None`` when the file is empty or when pass
is True).
"""
try:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING]: _check_file silently treats unreadable files as passing

Catching OSError and returning (True, None) means any file that cannot be read (permissions, deleted mid-scan, etc.) is silently treated as valid. This could mask real issues. Consider logging a warning or treating unreadable files as violations.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

def _run_check(repo: Path) -> tuple[int, str]:
"""Run the gate against *repo* and return (exit_code, stdout)."""
original = tng.REPO_ROOT
env = os.environ.copy()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION]: env variable is set but never used

The env dictionary is populated with TRAILING_NEWLINE_ROOT but never passed to check_main. The test relies on monkey-patching tng.REPO_ROOT instead. Remove the unused env variable to avoid confusion.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

assert "trailing-newline-gate: clean" in out

def test_red_no_newline(self, tmp_path):
"""fixture ends 0x2e (no newline) -> exit 1, offending path in output"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION]: Docstring inaccurately describes the fixture content

The docstring says "fixture ends 0x2e" but the string "### Release notes" ends with 0x73 ('s'), not 0x2e ('.'). Update the docstring to match the actual content.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

_write(repo, "benchmarks/data/README.md", "Pinned data")
rc, out = _run_check(repo)
assert rc == 1, f"expected exit 1, got {rc}: {out!r}"
assert "changelog.d/test.md" in out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION]: test_red_no_newline only asserts one of two failing paths

Both changelog.d/test.md and benchmarks/data/README.md lack trailing newlines, but only changelog.d/test.md is asserted. Add an assertion for benchmarks/data/README.md to fully verify the failure path.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

_write(repo, "benchmarks/data/README.md", "Pinned data\n\n")
rc, out = _run_check(repo)
assert rc == 1, f"expected exit 1, got {rc}: {out!r}"
assert "trailing-newline-gate FAIL" in out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION]: test_red_blank_line_at_eof does not verify both failing paths

Both files end with blank lines at EOF, but only the generic failure message is asserted. Add assertions for both changelog.d/test.md and benchmarks/data/README.md to fully verify the behavior.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 6 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 4
Issue Details (click to expand)

WARNING

File Line Issue
scripts/check_trailing_newline.py 38 _last_byte_hex is defined but never called (dead code)
scripts/check_trailing_newline.py 59 _check_file silently treats unreadable files as passing

SUGGESTION

File Line Issue
tests/test_trailing_newline_gate.py 53 env variable is set but never used
tests/test_trailing_newline_gate.py 76 Docstring says "fixture ends 0x2e" but content ends with 0x73
tests/test_trailing_newline_gate.py 82 test_red_no_newline only asserts one of two failing paths
tests/test_trailing_newline_gate.py 91 test_red_blank_line_at_eof does not verify both failing paths
Files Reviewed (3 files)
  • changelog.d/tsk-7m6ju5-trailing-newline-gate.md - no issues
  • scripts/check_trailing_newline.py - 2 issues
  • tests/test_trailing_newline_gate.py - 4 issues

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash · Input: 86.4K · Output: 20.5K · Cached: 610.4K

@jaylfc

jaylfc commented Aug 18, 2026

Copy link
Copy Markdown
Owner Author

BLOCK, but the gate script itself is correct and should be kept almost exactly as written. The byte logic is right, it reports the offending path and the actual hex byte, and I confirmed it goes red on a real offender rather than only on a fixture. The block is about one thing only: nothing in this repo ever runs it.

Reviewed as a trial merge into current master (dad7c0e8). The branch was cut from a base three commits behind, so the diff alone is not the tree that would land; the merge is clean and git diff origin/master --stat on it shows exactly the three added files and nothing riding along.

What I verified works

I dropped a real fragment changelog.d/zz-probe-offender.md whose last byte is 0x65 into the merged tree and ran the gate directly:

trailing-newline-gate FAIL:
  changelog.d/zz-probe-offender.md: last byte 0x65
exit=1

That is the positive control, and it passes. The 10 new tests also pass, and the empty-file, blank-line-at-EOF and hex-reporting cases are all genuinely covered.

Why it is blocked

grep -rln check_trailing_newline across *.yml, *.yaml, *.toml, *.cfg, *.ini, *.sh and Makefile returns no hits. The repo already has three sibling gates and every one of them has its own workflow: deleted-symbols-gate.yml, normalise-handle-gate.yml, witness-token-gate.yml. This PR adds a fourth gate script and no fourth workflow.

The consequence, measured end to end rather than reasoned about. With that 0x65 fragment still sitting in changelog.d/, the full suite on the trial merge returned:

1627 passed, 12 skipped in 162.58s

Baseline is 1617, so all 10 new gate tests ran and passed while the exact defect they exist to catch sat in the tree, untouched and unreported. Every test in the file examines a tmp_path fixture; not one of them looks at the real repository. As it stands this PR closes the card's stated gap on paper only: the defect that has now shipped in five PRs in a row would still ship.

One more finding

The documented TRAILING_NEWLINE_ROOT override has zero coverage. Line 54 of the test file builds env["TRAILING_NEWLINE_ROOT"] on a copy of os.environ and then discards it, because check_main is called in-process and no subprocess is ever spawned; the tests reach in and monkeypatch tng.REPO_ROOT instead. I settled that with a mutant rather than by reading: replacing line 30 with REPO_ROOT = _REPO_ROOT, ignoring the environment variable completely, still gives 10 passed. The docstring sells that override as what makes the gate usable for CI on staged changes, so it should either be covered or removed along with the dead env dict.

Minor: scripts/check_trailing_newline.py carries an em dash in the "Print nothing extra for empty files" comment.

Not wanted in the revision: no change to the byte-checking logic, and no widening of SCOPE_GLOBS. benchmarks/data/README.md is the only pinned data README that exists, so the current two globs are the right scope despite the plural in the title. I checked find benchmarks -name 'README*' to be sure.

Revision card tsk-jwpxo5 carries the exact end state: the missing workflow modelled on witness-token-gate.yml, a test that runs the gate over the real repository root so pytest catches it too, the override either covered or deleted, and the acceptance proof stated as two suite runs where the one with a bad fragment present must not read 1627 passed. Card tsk-7m6ju5 is closed with this PR.

@jaylfc jaylfc closed this Aug 18, 2026
@jaylfc

jaylfc commented Aug 18, 2026

Copy link
Copy Markdown
Owner Author

Correction to my review above: the revision card is tsk-n3eubn, not tsk-jwpxo5.

I closed tsk-jwpxo5 minutes after filing it, before any lane claimed it. It instructed the revision to add a test asserting on the real repository tree, and the original card tsk-7m6ju5 had explicitly forbidden exactly that, calling it "a vacuous test that passes for the wrong reason". Two cards in one lineage giving opposite instructions is the same contradiction I flag in other lanes, so I corrected it rather than leaving it to be discovered mid-implementation.

tsk-n3eubn carries the same findings and settles that question outright: the missing workflow is the one mandatory item, and a real-tree test is optional and welcome. My reasoning is that such a test is not in fact vacuous, since it is the only thing that makes a plain pytest run go red on a bad fragment, and it breaks only when a fragment is added badly, which is the point. But the workflow alone closes the gap, so it is not required.

One thing I should have said more plainly in the review: tsk-7m6ju5 is itself the root cause here. It specified the gate script and its fixture tests in detail and never once said how the gate gets run, and it steered away from the real tree on top of that. This PR built what the card asked for. The gap was in my card, not in the implementation.

jaylfc added a commit that referenced this pull request Aug 18, 2026
… ship green (#374)

The gate script from #368 was correct and is kept almost as-is, but nothing
ever invoked it: no workflow, Makefile, shell script or config referenced it,
and every one of its ten tests used a tmp_path fixture, so a fragment whose
last byte was not 0x0a sat in changelog.d unreported while the suite passed.

Adds .github/workflows/trailing-newline-gate.yml, modelled on the three
sibling gates, plus a test that runs the gate against the repository's own
tree so a plain pytest run goes red as well as CI.

Covers the documented TRAILING_NEWLINE_ROOT override, which had none: it is
read at import time, so the in-process tests could not reach it and replacing
it with the bare default left all ten green. The two new subprocess tests
fail under that mutant.

Also drops an em dash from a comment and terminates both new files with 0x0a,
which the gate exists to require.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant