fix(tests): the no-block guard reads the shell, not the comment explaining it (#3680)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 42s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m33s
CI & Build / Build & push image (push) Successful in 19s

The hook's header quotes `{"decision":"block"}` while explaining why this hook
must never emit one, and the instructions it prints use "decision" in a
sentence. Grepping the whole file caught both and failed the guard on the file
doing its job.

Strips comments and the heredoc first, and guards the stripper: `_code()`
returning nothing would make all three static checks pass against an empty
string, which is the circularity rule 167 is about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
2026-09-16 08:13:56 -04:00
co-authored by Claude Opus 5
parent 4b8d22e3ec
commit 47388eda36
+37 -4
View File
@@ -51,6 +51,39 @@ def _run(event: dict) -> subprocess.CompletedProcess:
capture_output=True, text=True, timeout=30) capture_output=True, text=True, timeout=30)
def _code() -> str:
"""The script with its comments and its heredoc removed.
The static checks below are about what the shell DOES. Run over the whole
file they also read the header — which quotes `{"decision":"block"}` in the
course of explaining why this hook must never emit one — and the emitted
instructions, which use the word "decision" in a sentence. Both are the
file doing its job, and neither is code.
"""
lines, in_heredoc = [], False
for line in HOOK.read_text().splitlines():
if in_heredoc:
in_heredoc = line.strip() != "EOF"
continue
if line.lstrip().startswith("cat <<'EOF'"):
in_heredoc = True
continue
if not line.lstrip().startswith("#"):
lines.append(line)
return "\n".join(lines)
def test_the_comment_stripper_still_leaves_the_shell_behind():
"""Guard on the three checks below it. `_code()` returning nothing would
make every one of them pass while checking an empty string — the same
circularity the plugin version check has to defend against."""
code = _code()
assert "set -uo pipefail" in code and "exit 0" in code
# It really did strip: both of the words the checks look for are present
# in the file, and neither is in the code.
assert "decision" in HOOK.read_text()
@pytest.mark.parametrize("trigger", ["manual", "auto"]) @pytest.mark.parametrize("trigger", ["manual", "auto"])
def test_it_exits_zero_with_instructions_on_stdout(trigger): def test_it_exits_zero_with_instructions_on_stdout(trigger):
"""Exit 0 plus non-empty stdout is the entire contract for reaching the """Exit 0 plus non-empty stdout is the entire contract for reaching the
@@ -78,9 +111,9 @@ def test_it_emits_text_and_not_a_json_envelope():
def test_it_never_blocks_the_compaction(): def test_it_never_blocks_the_compaction():
"""A block skips compaction silently from the model's side. Nothing in the """A block skips compaction silently from the model's side. Nothing in the
script may produce one — not an exit code, not a decision.""" script may produce one — not an exit code, not a decision."""
body = HOOK.read_text() code = _code()
assert '"decision"' not in body and "'decision'" not in body assert "decision" not in code, "a block decision would skip the compaction"
assert "exit 2" not in body assert "exit 2" not in code
# A truncated or absent event must not turn into a non-zero exit either. # A truncated or absent event must not turn into a non-zero exit either.
for event in ("", "not json", "{}"): for event in ("", "not json", "{}"):
out = subprocess.run(["bash", str(HOOK)], input=event, out = subprocess.run(["bash", str(HOOK)], input=event,
@@ -92,7 +125,7 @@ def test_additional_context_is_not_how_this_event_works():
"""SessionStart's channel, which does not exist on PreCompact. A rewrite """SessionStart's channel, which does not exist on PreCompact. A rewrite
that reaches for it would read as consistent with the other hooks and that reaches for it would read as consistent with the other hooks and
inject nothing at all.""" inject nothing at all."""
assert "additionalContext" not in HOOK.read_text().split("set -uo pipefail")[-1] assert "additionalContext" not in _code()
def test_the_plugin_registers_it_on_precompact(): def test_the_plugin_registers_it_on_precompact():