Store .bat files verbatim so bot commits stop reintroducing line ending churn - #4803
Merged
Merged
Conversation
Dependabot commits wrapper updates through the GitHub API, which writes blob bytes directly and bypasses .gitattributes eol normalization. Gradle generates gradlew.bat with CRLF, so every wrapper bump that rewrites the scripts deposits a CRLF blob that violates ext, leaving the file permanently modified in every clone and erupting as an 82-line diff on unrelated PRs. Marking .bat/.cmd as -text removes the canonical form entirely, so there is nothing left to drift from. perf-tests/gradlew.bat is converted to CRLF since -text checks out bytes verbatim.
Committed via the GitHub API by microsoft-github-policy-service[bot] in microsoft#3129 with CRLF, violating the repo-wide �ol=lf. Same root cause as the wrapper scripts: the file has been latently modified in every clone since then and would eventually surface as unrelated churn on someone else's PR.
xiang17
requested review from
harsimar,
mattsains-msft,
rajkumar-rangaraj,
ramthi and
trask
as code owners
July 30, 2026 23:18
rajkumar-rangaraj
approved these changes
Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
gradlew.batkeeps showing up as an 82-line diff on unrelated PRs. It has been"fixed" before (#4761) and came straight back. Recent collateral: #4746 and #4802
both picked it up while changing something else entirely.
Root cause
.gitattributesmarks*.batastext eol=crlf. Thetextattribute is a contractwith two halves: LF in the repository blob, CRLF in the working tree. That contract
is enforced by the git client, inside
git add/git checkout.Dependabot never runs a git client. It pushes through GitHub's Git Data API, where the
bytes it POSTs become the blob verbatim, bypassing
.gitattributesentirely. Gradlegenerates
gradlew.batwith CRLF, so those bytes land in the repo unnormalized.The result is a file that is permanently modified in every clone — the working tree
can never match the index once the clean filter runs.
git statushides this behindstat caching until something forces a re-hash (
git add -A,git stash, a tooltouching the file, git's racy-timestamp check on a fast CI checkout). Then it erupts on
whatever PR happens to be open.
History of the
gradlew.batblob shows a perfect correlation:Every commit made with a real git client produced LF; every Dependabot wrapper bump
produced CRLF. Nobody chose LF — git converted on their behalf during
git add.It only fires when the bump crosses a Gradle release whose script template actually
changed. #4780 rewrote the scripts and drifted; #4776 (9.6.0 → 9.6.1) only touched
distributionUrland didn't. That is why it feels random and why it will keephappening on an unpredictable schedule.
#4780 shows the whole story in one commit:
gradlewis a clean 4-line diff whilegradlew.batis 164 lines. Same template change, same bot. The only difference isthat the
.batgets stored with line endings that violate its attribute, burying thereal 2-line change under 82 lines of conversion noise.
Fix
Mark
.bat/.cmdas-text. Git then stores and checks out these files byte forbyte, with no conversion in either direction. There is no canonical form, so there is
nothing left to drift from — Dependabot, Copilot, Windows and Linux all agree by
construction. This is a permanent fix rather than another one-time cleanup.
Since
-textchecks out verbatim, every.batblob must be CRLF or it would ship abroken script to Windows.
gradlew.batalready is (which is why it does not appear inthis diff);
perf-tests/gradlew.batis converted here..github/policies/resourceManagement.ymlis the same bug from a different bot —microsoft-github-policy-service[bot]committed it via the API in #3129 with CRLF,violating the repo-wide
eol=lf. It has been latently modified in every clone sincethen and would eventually surface as unrelated churn on someone else's PR. Fixed here
in its own commit.
Why not the alternatives
text eol=crlfand renormalize — that is Normalize line endings for gradlew.bat files #4761, which lasted three wrapperbumps.
adds maintenance surface.
*.bat text !eol(CRLF only on Windows, LF in the index) — still requires LF inthe index, which is exactly what Dependabot cannot honor, so the drift is unchanged.
It would also make correctness depend on each developer's
core.autocrlf.What's in the diff
366 of those lines contain no content change whatsoever.
gradlew.batis untouched andbyte-identical to
main(8508ef684d). Reviewing with "Hide whitespace changes"reduces this to the
.gitattributesedit.Verification
git ls-files --eol '*.bat' '*.cmd'→ both filesi/crlf w/crlf attr/-textgit diff --ignore-cr-at-eol main...HEAD→ only.gitattributeshas real contentchanges; everything else is line endings
git ls-files --eol | grep -E 'i/crlf|i/mixed'→ only the two.batfiles, which isnow their intended state;
resourceManagement.ymlno longer appears, so no latentdrift remains anywhere in the repo
git add --renormalize .produces no changes — the tree is a fixed pointgit statusclean./gradlew.bat --versionandperf-tests/gradlew.bat --versionboth run on Windowsand report Gradle 9.6.1
Risk
-textgives up the self-healing property: if some tool ever wrote a.batwith LF, gitwould no longer silently correct it. In practice the only thing that writes these files
is Gradle's wrapper generator, which emits CRLF, and nobody hand-edits them.
Regressions are already covered —
.github/workflows/build-common.ymlruns./gradlew assembleonwindows-2022(line 82) plus a Windows test matrix (line 142),and
./gradlewresolves togradlew.batthere, so an LF.batfails on the first PR.One gap worth noting:
perf-tests/gradlew.batis not exercised by CI (perf tests run onubuntu-latest), so a regression there would only surface when running the perf harnesson Windows.