auto-promote: single quotes in regex break single-quoted TOML literals #12

Open
opened 2026-04-24 15:15:02 +00:00 by jbr870 · 0 comments
Owner

Problem

The auto-promote writer emits regex patterns as single-quoted TOML literal strings:

command = '{pattern}'

TOML single-quoted literals do not support any form of escaping — they cannot contain a literal '. When the LLM suggests a regex that includes a single quote character (typically inside a character class like ['"]?), the generated line parses as a truncated string followed by garbage:

command = 'curl\s+.*-w\s+['"]?%\{http_code\}['"]?'
                         ^ string terminates here, rest is invalid

toml::from_str then fails with expected newline, \#`` and — as in #11 — all auto-promoted rules after the broken block are silently dropped.

Impact

  • Same fail-open silent-data-loss pattern as #11: users lose learned rules with no visible signal except a stderr line on every validate / report invocation.
  • Observed live on line 1292 of ~/.claude/claude-permit/auto-rules.toml after repairing the #11 damage.
  • Confirmed recurrence (2026-04-25): a second corruption window opened between the multiline fix and now. Four corrupt lines in auto-rules.toml (1292, 4606, 5774, 7244), all from regexes matching shell-quoted args (['"]…['"]). The earliest (line 1292, dated 2026-04-16) aborts toml::from_str first → all 1100+ promoted rules silently dropped at runtime → user experience: "I keep granting the same permissions; nothing seems to be learned." claude-permit validate does report the parse error, but the message scrolls past at hook time and there is no other visible signal.

Affected code

src/auto_promote.rs::format_rule_toml — writes each field as {key} = '{pattern}' with no awareness of single quotes in {pattern}.

src/config.rs::load_and_merge_auto_rules (lines 262-274) — on parse failure, logs to stderr and returns. The whole file is dropped on a single bad block; there is no per-rule fault tolerance and no visible health signal beyond stderr.

Proposed fix

The fix has two halves: stop writing corrupt rules, and survive corrupt rules without losing data or hiding the problem.

1. Quote-safe emit (prevention)

Switch to double-quoted TOML strings when the pattern contains ', escaping \, ", and control chars per the TOML spec. Preserves single-quoted literals for the common case (keeps the "no double-backslash pain" benefit). Add a regression test that round-trips a rule whose regex pattern contains ' through toml::from_str, in the same shape as the multiline regression test added in #11.

2. Per-rule fault tolerance + loud failure (resilience)

Skipping individual bad rules is the right behavior, but every skip MUST leave a paper trail in multiple places — otherwise we trade the #11/#12 silent-loss failure mode for a subtler "10 % of rules vanished and nothing told me" failure mode.

Required signals when one or more rules are skipped:

  • Audit log entry. Emit a new event: "AutoRulesHealth" JSONL line on hook startup whenever the load skips one or more blocks: {skipped_count, total_count, sample_errors: [{line, error}, …]}. This makes the signal queryable and feeds the report.
  • Report banner. claude-permit report shows a prominent banner at the top: "⚠ N auto-rules unparseable — being ignored. Run claude-permit validate to inspect."
  • validate exits non-zero when auto-rules has skipped blocks, prints them with line numbers and parse errors. validate becomes a real health check, not just a syntax check.
  • First-of-session stderr warning (optional): on the first hook invocation per session, emit a one-line stderr warning if auto-rules has skipped blocks. Stderr from hooks surfaces in Claude Code's debug view.

3. Corruption-rate guardrail (catastrophe protection)

If more than ~5 % of auto-rules fail to parse, treat it as a corruption event rather than a routine skip: emit a higher-severity audit entry and either fail closed (force user attention before the next hook fires) or, at minimum, surface the warning on every hook invocation until acknowledged. This is the backstop that prevents a future analogue of #11/#12 from quietly invalidating most of the user's promoted rules.

4. One-time cleanup

The four corrupt lines currently in auto-rules.toml (1292, 4606, 5774, 7244) need a one-time cleanup before any of the above helps the existing live install. auto-rules.toml.bak (Apr 24) is the reference point.

Acceptance criteria

  • New rules with ' in the regex round-trip through toml::from_str.
  • A corrupt block in auto-rules.toml no longer prevents the surrounding rules from loading.
  • A corrupt block produces an AutoRulesHealth audit entry, a banner in claude-permit report, and a non-zero exit from claude-permit validate.
  • auto-rules.toml is repaired in the live install and validate exits clean.

Discovery context

Found while repairing existing damage caused by issue #11. The multi-line-leak repair revealed single-quote handling as a second, independent bug in the same writer path. The 2026-04-25 recurrence confirmed that fixing the writer alone is not sufficient — the loader also needs to fail loudly per-rule rather than fail-open silently per-file.

  • #11 — multi-line command summaries leak into raw TOML (fixed)
## Problem The auto-promote writer emits regex patterns as single-quoted TOML literal strings: ```toml command = '{pattern}' ``` TOML single-quoted literals do **not** support any form of escaping — they cannot contain a literal `'`. When the LLM suggests a regex that includes a single quote character (typically inside a character class like `['"]?`), the generated line parses as a truncated string followed by garbage: ```toml command = 'curl\s+.*-w\s+['"]?%\{http_code\}['"]?' ^ string terminates here, rest is invalid ``` `toml::from_str` then fails with `expected newline, \`#\`` and — as in #11 — all auto-promoted rules after the broken block are silently dropped. ## Impact - Same fail-open silent-data-loss pattern as #11: users lose learned rules with no visible signal except a stderr line on every `validate` / `report` invocation. - Observed live on line 1292 of `~/.claude/claude-permit/auto-rules.toml` after repairing the #11 damage. - Confirmed recurrence (2026-04-25): a second corruption window opened between the multiline fix and now. Four corrupt lines in `auto-rules.toml` (1292, 4606, 5774, 7244), all from regexes matching shell-quoted args (`['"]…['"]`). The earliest (line 1292, dated 2026-04-16) aborts `toml::from_str` first → all 1100+ promoted rules silently dropped at runtime → user experience: "I keep granting the same permissions; nothing seems to be learned." `claude-permit validate` does report the parse error, but the message scrolls past at hook time and there is no other visible signal. ## Affected code `src/auto_promote.rs::format_rule_toml` — writes each field as `{key} = '{pattern}'` with no awareness of single quotes in `{pattern}`. `src/config.rs::load_and_merge_auto_rules` (lines 262-274) — on parse failure, logs to stderr and returns. The whole file is dropped on a single bad block; there is no per-rule fault tolerance and no visible health signal beyond stderr. ## Proposed fix The fix has two halves: stop writing corrupt rules, and survive corrupt rules without losing data or hiding the problem. ### 1. Quote-safe emit (prevention) Switch to double-quoted TOML strings when the pattern contains `'`, escaping `\`, `"`, and control chars per the TOML spec. Preserves single-quoted literals for the common case (keeps the "no double-backslash pain" benefit). Add a regression test that round-trips a rule whose regex pattern contains `'` through `toml::from_str`, in the same shape as the multiline regression test added in #11. ### 2. Per-rule fault tolerance + loud failure (resilience) Skipping individual bad rules is the right behavior, but every skip MUST leave a paper trail in multiple places — otherwise we trade the #11/#12 silent-loss failure mode for a subtler "10 % of rules vanished and nothing told me" failure mode. Required signals when one or more rules are skipped: - **Audit log entry.** Emit a new `event: "AutoRulesHealth"` JSONL line on hook startup whenever the load skips one or more blocks: `{skipped_count, total_count, sample_errors: [{line, error}, …]}`. This makes the signal queryable and feeds the report. - **Report banner.** `claude-permit report` shows a prominent banner at the top: "⚠ N auto-rules unparseable — being ignored. Run `claude-permit validate` to inspect." - **`validate` exits non-zero** when auto-rules has skipped blocks, prints them with line numbers and parse errors. `validate` becomes a real health check, not just a syntax check. - **First-of-session stderr warning** (optional): on the first hook invocation per session, emit a one-line stderr warning if auto-rules has skipped blocks. Stderr from hooks surfaces in Claude Code's debug view. ### 3. Corruption-rate guardrail (catastrophe protection) If more than ~5 % of auto-rules fail to parse, treat it as a corruption event rather than a routine skip: emit a higher-severity audit entry and either fail closed (force user attention before the next hook fires) or, at minimum, surface the warning on every hook invocation until acknowledged. This is the backstop that prevents a future analogue of #11/#12 from quietly invalidating most of the user's promoted rules. ### 4. One-time cleanup The four corrupt lines currently in `auto-rules.toml` (1292, 4606, 5774, 7244) need a one-time cleanup before any of the above helps the existing live install. `auto-rules.toml.bak` (Apr 24) is the reference point. ## Acceptance criteria - New rules with `'` in the regex round-trip through `toml::from_str`. - A corrupt block in `auto-rules.toml` no longer prevents the surrounding rules from loading. - A corrupt block produces an `AutoRulesHealth` audit entry, a banner in `claude-permit report`, and a non-zero exit from `claude-permit validate`. - `auto-rules.toml` is repaired in the live install and `validate` exits clean. ## Discovery context Found while repairing existing damage caused by issue #11. The multi-line-leak repair revealed single-quote handling as a second, independent bug in the same writer path. The 2026-04-25 recurrence confirmed that fixing the writer alone is not sufficient — the loader also needs to fail loudly per-rule rather than fail-open silently per-file. ## Related - #11 — multi-line command summaries leak into raw TOML (fixed)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jbr870/claude-permit#12
No description provided.