Hooks: a substitution's own quotes are read as the shell reads them, and a printer's name is searched once (#605)

Merged
DO
1d ago
+145-21

Part of #605, "Hooks: two readings left from #604 that match main's existing behaviour". It fixes items (a), (b) and (c). Items (d), (e), (f) and the new (g) stay open there.

Hooks are mistake guards (decision 0038's triage rule), so this is held to that standard.

What changes

ItemBeforeNow
(a), (c): shell._insideCounted every " since a held substitution opened, a " in a nested substitution, a comment or single quotes among themReads a substitution's own quotes as the shell does: one in a nested substitution is that one's own; a comment or single quotes outside its double quotes hold none. A comment begins where _held begins one: not in a ${...}, not after a substitution's ), not after a character a backslash holds, and at a backtick's start; in a backtick it ends at the backtick
(b): identity._definedSearched the whole command once for each command that prints: quadratic, refused at about 4,000 short commandsKeeps each name's answer for the reading: linear. The whole check is now timed on that spelling, beside the reader

The spellings from the issue that hide an rm are now asked about; main read three of them and let them through. The spellings the reviewers found on the first head are asked about too.

Checks

  • tests/test_hooks.py::test_a_substitutions_own_quotes_are_read_as_the_shell_reads_them: eleven spellings that are asked about, and two that read as before.
  • tests/test_hooks.py::test_whether_a_command_defines_a_printers_name_is_searched_once_a_name, and a linear-time entry for the whole check.
  • Decision 0032 gains two rows, naming 12 new mutants; 4 older mutants follow the code.
  • At bc8d9867: make verify passes; check_decisions is clean; all 314 mutants of tools/hooks/shell.py and tools/hooks/identity.py are killed.

Reviews

All read the first head, e6902c4e. The fixes are in the later commits.

ReaderFindingsDisposition
Argon (gemini-4-argon-high)2 major, 5 spellingsFixed in abc691f8 and bc8d9867. The " nested in a double-quoted ${...} is asked about at the head
MuseNot run (quota: 429 until 2026-11-09T00:00Z), owed—
Astra, standing in for Muse1 major: # in ${x:- #}Fixed in abc691f8
Claude review1 major: an untested nested branch; 1 minorTest and mutants added in 73c6e686. The minor, a gap that is the same on main, is filed as item (g) of #605
Claude security pass1 major: $(date)# read as a commentFixed in abc691f8

šŸ¤– Generated with Claude Code