Skip to content

Commit 6894146

Browse files
ptr727claude
andcommitted
Normalize a Bare CR Too, Through One Helper Both Digests Share
`payload_digest` reduced CRLF and left a bare CR alone, while the installer reads a snippet in text mode, where a bare CR arrives as a newline and installs as one. A snippet carrying CR-only endings therefore installed normalized content and hashed to something else, and the machine was reported STALE against its own content. Four sites were doing this replacement by hand and two of them disagreed, which is the arrangement that produced the defect. They all call one helper now, so the two digests cannot normalize differently. Two cases added. The first builds the CR-only variant from the normalized form rather than by replacing newlines in the file, because these snippets are CRLF in this repository and a blind replace produces a doubled CR rather than a bare one. My first version of that test made exactly that mistake and failed against the fixed code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 23e80ac commit 6894146

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

‎host-setup/agent-safety/install.py‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,19 @@ def git(*args):
136136
returnref
137137

138138

139+
defnormalized(data):
140+
"""Line endings reduced to newlines, covering CRLF and a bare CR.
141+
142+
One helper rather than a replace at each site. Both digests have to normalize identically or a
143+
machine drifts on nothing, and a site that handled CRLF while missing CR did exactly that: the
144+
installer reads a snippet in text mode, so a bare CR arrives as a newline and installs as one,
145+
while a digest that left it alone reported the machine STALE against its own content.
146+
"""
147+
ifisinstance(data, bytes):
148+
returndata.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
149+
returndata.replace("\r\n", "\n").replace("\r", "\n")
150+
151+
139152
defpayload_digest():
140153
"""One digest over the content this kit installs, normalized the way the installer writes it.
141154
@@ -150,7 +163,7 @@ def payload_digest():
150163
"""
151164
h=hashlib.sha256()
152165
fornameinPAYLOAD_FILES:
153-
raw=(HERE/name).read_bytes().replace(b"\r\n", b"\n")
166+
raw=normalized((HERE/name).read_bytes())
154167
# A snippet is embedded stripped, so trailing whitespace is not installed content.
155168
# The hook is copied byte for byte, so nothing about it is stripped.
156169
ifname.endswith(".md"):
@@ -213,8 +226,8 @@ def installed_digest(claude_home):
213226
ifnothook.is_file() ornotclaude_md.is_file():
214227
returnNone
215228
h=hashlib.sha256()
216-
h.update(hook.read_bytes().replace(b"\r\n", b"\n"))
217-
text=claude_md.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
229+
h.update(normalized(hook.read_bytes()))
230+
text=normalized(claude_md.read_text(encoding="utf-8", errors="replace"))
218231
formarkerin ("agent-safety", "fleet-bootstrap"):
219232
found=re.search(rf"<!-- {marker} v\d+ start -->.*?<!-- {marker} v\d+ end -->", text, re.DOTALL)
220233
ifnotfound:
@@ -536,7 +549,7 @@ def reject(where, held, want):
536549
ifclaude_md.exists():
537550
raw=claude_md.read_bytes()
538551
newline="\r\n"ifb"\r\n"inrawelse"\n"
539-
existing=raw.decode("utf-8").replace("\r\n", "\n").replace("\r", "\n")
552+
existing=normalized(raw.decode("utf-8"))
540553
else:
541554
newline, existing="\n", ""
542555
formarker, filenameinblocks:

‎host-setup/agent-safety/test_install.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,29 @@ def test_trailing_whitespace_on_a_snippet_is_not_reported_as_drift(self):
412412
finally:
413413
target.write_bytes(original)
414414

415+
deftest_a_bare_cr_in_a_snippet_is_not_reported_as_drift(self):
416+
"""The installer reads snippets in text mode, so a bare CR arrives and installs as a newline.
417+
418+
A digest normalizing CRLF but not CR reported the machine STALE against its own content.
419+
"""
420+
baseline=install.payload_digest()
421+
target=HERE/"claude-md-safety.md"
422+
original=target.read_bytes()
423+
try:
424+
# Built from the normalized form, since the snippets are CRLF in this repo.
425+
# A blind newline replace would turn each CRLF into a doubled CR rather than a bare one.
426+
target.write_bytes(install.normalized(original).replace(b"\n", b"\r"))
427+
self.assertEqual(install.payload_digest(), baseline)
428+
finally:
429+
target.write_bytes(original)
430+
431+
deftest_every_normalization_site_agrees(self):
432+
"""The two digests must normalize identically, or a machine drifts against nothing."""
433+
forraw, wantin ((b"a\r\nb", b"a\nb"), (b"a\rb", b"a\nb"), (b"a\nb", b"a\nb")):
434+
self.assertEqual(install.normalized(raw), want)
435+
forraw, wantin (("a\r\nb", "a\nb"), ("a\rb", "a\nb"), ("a\nb", "a\nb")):
436+
self.assertEqual(install.normalized(raw), want)
437+
415438
deftest_a_real_edit_to_a_snippet_is_still_reported(self):
416439
"""The normalization must not swallow a change that does reach the installed block."""
417440
baseline=install.payload_digest()

0 commit comments

Comments
 (0)