Skip to content

Commit 6583586

Browse files
ptr727claude
andcommitted
Compare the Installed Bytes, Not Just the Markers That Delimit Them
Four review findings, all real. `--report` compared marker versions and the source payload, and never what is actually on the machine. A block edited between its own markers left the version untouched and reported CURRENT, which is most of what "has someone weakened this by hand" means. The deployed hook is not marker-delimited at all, so a modified or deleted one was invisible the same way. The installed bytes are now digested and compared against what this checkout would write: the hook, and each block as it appears in CLAUDE.md. Line endings are normalized first, so a machine holding identical text with CRLF is current rather than drifted. `blocks_present` accepted any equal number of start and end markers, so a duplicated block reported present and named the first version while the second silently governed. It now requires exactly one pair. `source_ref` let FileNotFoundError escape when git is absent, crashing both the install and the read-only report on exactly the minimal host a tarball install targets. It records `vcs: none` instead. `report` read a parsed stamp straight into `stamp_line`, so a hand-edited or partially written file raised KeyError rather than returning a verdict. Required keys are checked first. Ten cases added. Nine fail against the previous code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent f938b0e commit 6583586

2 files changed

Lines changed: 160 additions & 3 deletions

File tree

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

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,12 @@ def source_ref():
116116
to prevent. A checkout that is not a git tree at all (an extracted tarball) says so.
117117
"""
118118
defgit(*args):
119-
r=subprocess.run(["git", "-C", str(HERE), *args], capture_output=True, text=True)
119+
# A host with no git is the normal case for a tarball install, and it is not an error here.
120+
# Letting FileNotFoundError escape would crash both the install and the read-only report.
121+
try:
122+
r=subprocess.run(["git", "-C", str(HERE), *args], capture_output=True, text=True)
123+
exceptOSError:
124+
returnNone
120125
returnr.stdout.strip() ifr.returncode==0elseNone
121126

122127
sha=git("rev-parse", "HEAD")
@@ -155,25 +160,67 @@ def blocks_present(claude_md):
155160
found= {}
156161
formarkerin ("agent-safety", "fleet-bootstrap"):
157162
# A start marker alone is a half-written block, which a presence check reads as installed.
163+
# Exactly one pair, since the installer writes one and a duplicate is a corrupted file.
164+
# Two blocks mean the second silently governs, and reporting the first as current hides that.
158165
starts=re.findall(rf"<!-- {marker} (v\d+) start -->", text)
159166
ends=re.findall(rf"<!-- {marker} (v\d+) end -->", text)
160-
ifstartsandstarts==ends:
167+
iflen(starts) ==1andstarts==ends:
161168
found[marker] =starts[0]
162169
returnfound
163170

164171

172+
definstalled_digest(claude_home):
173+
"""A digest over the bytes actually on this machine, or None where the kit is not fully there.
174+
175+
Markers and versions answer whether a block is present, and nothing about its content, so a
176+
block edited between its own markers reports current under a presence check. The hook is not
177+
marker-delimited at all, so a modified or deleted one is invisible the same way.
178+
179+
Line endings are normalized first: CLAUDE.md keeps whatever endings it had, and a machine that
180+
holds identical text with CRLF is current rather than drifted.
181+
"""
182+
hook=claude_home/"hooks"/"gh-write-guard.py"
183+
claude_md=claude_home/"CLAUDE.md"
184+
ifnothook.is_file() ornotclaude_md.is_file():
185+
returnNone
186+
h=hashlib.sha256()
187+
h.update(hook.read_bytes().replace(b"\r\n", b"\n"))
188+
text=claude_md.read_text(encoding="utf-8", errors="replace").replace("\r\n", "\n")
189+
formarkerin ("agent-safety", "fleet-bootstrap"):
190+
found=re.search(rf"<!-- {marker} v\d+ start -->.*?<!-- {marker} v\d+ end -->", text, re.DOTALL)
191+
ifnotfound:
192+
returnNone
193+
h.update(found.group(0).encode("utf-8"))
194+
returnh.hexdigest()[:16]
195+
196+
197+
defexpected_installed_digest():
198+
"""The same digest computed from this checkout, naming what a run here would leave behind."""
199+
h=hashlib.sha256()
200+
h.update((HERE/"gh-write-guard.py").read_bytes().replace(b"\r\n", b"\n"))
201+
forfilenamein ("claude-md-safety.md", "claude-md-fleet.md"):
202+
h.update((HERE/filename).read_text(encoding="utf-8").strip().replace("\r\n", "\n").encode("utf-8"))
203+
returnh.hexdigest()[:16]
204+
205+
165206
defbuild_stamp(claude_home, installed):
166207
"""The record written to the machine after an install, or computed live for a report."""
167208
return {
168209
"stampVersion": STAMP_VERSION,
169210
"host": host_facts(),
170211
"source": source_ref(),
171212
"payloadDigest": payload_digest(),
213+
"installedDigest": installed_digest(claude_home),
172214
"blocks": blocks_present(claude_home/"CLAUDE.md"),
173215
"installedUtc": installed,
174216
}
175217

176218

219+
# Checked before a stamp is read, so a hand-edited or older-format file gives a verdict rather than a traceback.
220+
# A partial write produces valid JSON with keys missing.
221+
STAMP_REQUIRED= ("host", "source", "payloadDigest", "blocks", "installedUtc")
222+
223+
177224
defstamp_line(stamp):
178225
"""One line naming the machine and what it carries, short enough to paste into a checklist."""
179226
host=stamp["host"]
@@ -205,13 +252,26 @@ def report(claude_home):
205252
except (json.JSONDecodeError, OSError) ase:
206253
sys.stderr.write(f"Stamp at {path} is unreadable ({e}). Re-run the installer to rewrite it.\n")
207254
return2
255+
# Valid JSON is not a usable stamp: a hand edit or an older format parses and then breaks the read.
256+
missing= [kforkinSTAMP_REQUIREDifknotinstamp] ifisinstance(stamp, dict) else ["everything"]
257+
ifmissing:
258+
sys.stderr.write(f"Stamp at {path} is missing {', '.join(missing)}. "
259+
"Re-run the installer to rewrite it.\n")
260+
return2
208261
print(f"This machine: {stamp_line(stamp)}")
209-
# The stamp says what was installed; the file says what is there now.
262+
# The stamp says what was installed; the machine says what is there now.
210263
# A block edited or deleted by hand since the install makes both true and only the second current.
211264
live=blocks_present(claude_home/"CLAUDE.md")
212265
problems= []
213266
ifstamp.get("payloadDigest") !=current:
214267
problems.append("payload digest differs from this checkout")
268+
# Markers answer presence and say nothing about content, so the installed bytes are compared too.
269+
# This is what catches a block edited between its own markers, and a modified or deleted hook.
270+
live_installed=installed_digest(claude_home)
271+
iflive_installedisNone:
272+
problems.append("the deployed hook or CLAUDE.md is missing, so the kit is not fully installed")
273+
eliflive_installed!=expected_installed_digest():
274+
problems.append("the installed content differs from what this checkout would write")
215275
iflive!=stamp.get("blocks"):
216276
problems.append(f"CLAUDE.md now holds {liveor'no blocks'}, where the stamp recorded {stamp.get('blocks') or'none'}")
217277
ifstamp.get("source", {}).get("dirty"):

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,103 @@ def test_an_absent_file_yields_no_blocks_rather_than_raising(self):
122122
self.assertEqual(install.blocks_present(self.home/"nothing.md"), {})
123123

124124

125+
classTestInstalledContent(StampCase):
126+
"""Presence is not currency. These are the cases markers and versions cannot see."""
127+
128+
deftest_a_block_edited_between_its_own_markers_reports_stale(self):
129+
"""The marker and version are untouched, so a presence check calls this machine current."""
130+
self.install()
131+
text=self.md.read_text(encoding="utf-8")
132+
edited=text.replace("<!-- agent-safety v1 start -->",
133+
"<!-- agent-safety v1 start -->\nSomeone weakened this rule by hand.")
134+
self.assertNotEqual(edited, text)
135+
self.md.write_text(edited, encoding="utf-8")
136+
# Presence is unchanged: the markers and versions still read exactly as before.
137+
self.assertEqual(install.blocks_present(self.md), {"agent-safety": "v1", "fleet-bootstrap": "v1"})
138+
r=run(self.home, "--report")
139+
self.assertEqual(r.returncode, 1, r.stdout+r.stderr)
140+
self.assertIn("installed content differs", r.stdout)
141+
142+
deftest_a_modified_hook_reports_stale(self):
143+
"""The hook is not marker-delimited, so nothing else on this machine would notice."""
144+
self.install()
145+
hook=self.home/"hooks"/"gh-write-guard.py"
146+
hook.write_text(hook.read_text(encoding="utf-8") +"\n# neutered\n", encoding="utf-8")
147+
r=run(self.home, "--report")
148+
self.assertEqual(r.returncode, 1, r.stdout+r.stderr)
149+
self.assertIn("installed content differs", r.stdout)
150+
151+
deftest_a_deleted_hook_reports_stale_rather_than_crashing(self):
152+
self.install()
153+
(self.home/"hooks"/"gh-write-guard.py").unlink()
154+
r=run(self.home, "--report")
155+
self.assertEqual(r.returncode, 1, r.stdout+r.stderr)
156+
self.assertIn("not fully installed", r.stdout)
157+
158+
deftest_identical_content_with_crlf_is_current_rather_than_stale(self):
159+
"""CLAUDE.md keeps the endings it had, and a Windows host is not drifted for that alone."""
160+
self.install()
161+
raw=self.md.read_bytes()
162+
self.md.write_bytes(raw.replace(b"\n", b"\r\n"))
163+
r=run(self.home, "--report")
164+
self.assertEqual(r.returncode, 0, r.stdout+r.stderr)
165+
self.assertIn("CURRENT", r.stdout)
166+
167+
deftest_reinstalling_clears_an_edited_block(self):
168+
self.install()
169+
text=self.md.read_text(encoding="utf-8")
170+
self.md.write_text(text.replace("<!-- agent-safety v1 start -->",
171+
"<!-- agent-safety v1 start -->\nedited"), encoding="utf-8")
172+
self.assertEqual(run(self.home, "--report").returncode, 1)
173+
self.install()
174+
self.assertEqual(run(self.home, "--report").returncode, 0)
175+
176+
177+
classTestDuplicateBlocks(StampCase):
178+
deftest_a_duplicated_block_is_not_reported_as_present(self):
179+
"""Two blocks mean the second silently governs, and naming the first hides that."""
180+
self.install()
181+
text=self.md.read_text(encoding="utf-8")
182+
block=re.search(r"<!-- agent-safety v1 start -->.*?<!-- agent-safety v1 end -->",
183+
text, re.DOTALL).group(0)
184+
self.md.write_text(text+"\n"+block+"\n", encoding="utf-8")
185+
self.assertNotIn("agent-safety", install.blocks_present(self.md))
186+
187+
deftest_a_duplicated_block_reports_stale_rather_than_current(self):
188+
self.install()
189+
text=self.md.read_text(encoding="utf-8")
190+
block=re.search(r"<!-- agent-safety v1 start -->.*?<!-- agent-safety v1 end -->",
191+
text, re.DOTALL).group(0)
192+
self.md.write_text(text+"\n"+block+"\n", encoding="utf-8")
193+
r=run(self.home, "--report")
194+
self.assertEqual(r.returncode, 1, r.stdout+r.stderr)
195+
196+
197+
classTestDegradedEnvironments(StampCase):
198+
deftest_a_host_without_git_stamps_rather_than_crashing(self):
199+
"""A tarball install on a minimal host has no git, which is normal rather than an error."""
200+
env=dict(os.environ, CLAUDE_HOME=str(self.home), PATH="")
201+
r=subprocess.run([sys.executable, str(INSTALL)], capture_output=True, text=True, env=env)
202+
self.assertEqual(r.returncode, 0, r.stdout+r.stderr)
203+
stamp=json.loads(self.stamp.read_text(encoding="utf-8"))
204+
self.assertEqual(stamp["source"]["vcs"], "none")
205+
206+
deftest_a_stamp_missing_required_keys_gives_a_verdict_rather_than_a_traceback(self):
207+
self.install()
208+
self.stamp.write_text(json.dumps({"stampVersion": 1}) +"\n", encoding="utf-8")
209+
r=run(self.home, "--report")
210+
self.assertEqual(r.returncode, 2)
211+
self.assertIn("missing", r.stderr)
212+
self.assertNotIn("Traceback", r.stderr)
213+
214+
deftest_a_stamp_holding_a_non_object_gives_a_verdict_rather_than_a_traceback(self):
215+
self.install()
216+
self.stamp.write_text("[]\n", encoding="utf-8")
217+
r=run(self.home, "--report")
218+
self.assertEqual(r.returncode, 2)
219+
self.assertNotIn("Traceback", r.stderr)
220+
221+
125222
classTestStampContent(StampCase):
126223
deftest_the_stamp_names_the_machine_the_source_and_what_was_installed(self):
127224
self.install()

0 commit comments

Comments
 (0)