auto-promote: multi-line command summaries leak into raw TOML #11

Closed
opened 2026-04-24 14:54:29 +00:00 by jbr870 · 1 comment
Owner

Problem

The auto-promote writer generates per-rule TOML comments like:

# Auto-promoted 2026-04-09T13:55:04Z: Bash: # Check ages of pending rules
head -1 ~/.claude/claude-permi...

When the summary contains a newline (multi-line commands, heredocs, chained shell), the continuation lines are written as raw TOML instead of being prefixed with #. The parser then fails:

TOML parse error at line 94, column 6
   |
94 | head -1 ~/.claude/claude-permi...
   |      ^
expected `.`, `=`

Impact

toml::from_str fails on the first parse error, so all auto-promoted rules after the first broken block are silently dropped. The fail-open design keeps claude-permit working, but users lose their learned rules with no visible indication.

Found live in ~/.claude/claude-permit/auto-rules.toml — 184 leaked lines across many auto-promoted blocks.

Affected code

src/auto_promote.rs — the format_summary / comment-writer path writes summaries as-is without newline escaping.

Proposed fix

In the comment writer, replace any newline in the summary with a space (or split into multiple #-prefixed continuation lines). Round-trip through parse_auto_rules_with_metadata should be exercised by a regression test using a multi-line summary.

Repair for existing damage

A one-time script to prefix every non-TOML, non-blank, non-comment line with # would restore parsing of the existing file. Alternative: delete the affected auto-promoted blocks (cleaner but loses the rules).

Discovery context

Hit while running the new report --html subcommand (feature 10-report-html). The report itself rendered fine (fail-open), but the validate and report paths both log the parse error on every invocation.

## Problem The auto-promote writer generates per-rule TOML comments like: ``` # Auto-promoted 2026-04-09T13:55:04Z: Bash: # Check ages of pending rules head -1 ~/.claude/claude-permi... ``` When the summary contains a newline (multi-line commands, heredocs, chained shell), the continuation lines are written as raw TOML instead of being prefixed with `#`. The parser then fails: ``` TOML parse error at line 94, column 6 | 94 | head -1 ~/.claude/claude-permi... | ^ expected `.`, `=` ``` ## Impact `toml::from_str` fails on the first parse error, so **all** auto-promoted rules after the first broken block are silently dropped. The fail-open design keeps claude-permit working, but users lose their learned rules with no visible indication. Found live in `~/.claude/claude-permit/auto-rules.toml` — 184 leaked lines across many auto-promoted blocks. ## Affected code `src/auto_promote.rs` — the `format_summary` / comment-writer path writes summaries as-is without newline escaping. ## Proposed fix In the comment writer, replace any newline in the summary with a space (or split into multiple `#`-prefixed continuation lines). Round-trip through `parse_auto_rules_with_metadata` should be exercised by a regression test using a multi-line summary. ## Repair for existing damage A one-time script to prefix every non-TOML, non-blank, non-comment line with `#` would restore parsing of the existing file. Alternative: delete the affected auto-promoted blocks (cleaner but loses the rules). ## Discovery context Hit while running the new `report --html` subcommand (feature 10-report-html). The report itself rendered fine (fail-open), but the validate and report paths both log the parse error on every invocation.
Author
Owner

Fixed in ff23494 (merged to main).

Root cause: format_rule_toml rendered summary and command verbatim, so any embedded newlines broke out of the # comment line into raw TOML.

Fix: Strip \r / \n from both fields before rendering, and switch truncation from byte-indexing (&s[..60]) to char-safe chars().take(60) to avoid multibyte panics.

Regression coverage:

  • test_format_rule_toml_multiline_command_stays_one_comment_line — round-trips a multi-line Bash command through toml::from_str and asserts every pre-[[allow]] non-blank line starts with #.
  • test_format_rule_toml_multibyte_command_truncation — 80-char multibyte command, asserts the block still parses.

Live binary at ~/.claude/bin/claude-permit now contains the fix.

Fixed in [ff23494](https://git.wihslon.com/jbr870/claude-permit/commit/ff23494) (merged to main). **Root cause:** `format_rule_toml` rendered `summary` and `command` verbatim, so any embedded newlines broke out of the `#` comment line into raw TOML. **Fix:** Strip `\r` / `\n` from both fields before rendering, and switch truncation from byte-indexing (`&s[..60]`) to char-safe `chars().take(60)` to avoid multibyte panics. **Regression coverage:** - `test_format_rule_toml_multiline_command_stays_one_comment_line` — round-trips a multi-line Bash command through `toml::from_str` and asserts every pre-`[[allow]]` non-blank line starts with `#`. - `test_format_rule_toml_multibyte_command_truncation` — 80-char multibyte command, asserts the block still parses. Live binary at `~/.claude/bin/claude-permit` now contains the fix.
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#11
No description provided.