Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/guardrails/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,5 @@
"min": 1
}
},
"version": "0.38.0"
"version": "0.38.1"
}
27 changes: 27 additions & 0 deletions plugins/guardrails/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@
All notable changes to the `guardrails` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.38.1] - 2026-09-27

### Fixed

- **A long Bash or PowerShell command no longer runs the guard row past its 60-second `timeout`**
([#4528](https://github.com/melodic-software/claude-code-plugins/issues/4528)). Claude Code cancels a
command hook at its `timeout`, and on `PreToolUse` a cancelled command hook does not block the tool call
([hooks: Timeouts](https://code.claude.com/docs/en/hooks#timeouts)), so a long enough command passed every
guard on the row unchecked. On `main` the row took 7.23 s for a 10 KB heredoc and 12.6 s for 16 KB, and a
~70 KB one was still running at 120 s. Now it takes 132 ms, 203 ms and 64 ms.
- The row passes `--max-command-len 16384` to `run-guards.sh`. That is the `MAX_COMMAND_LEN` ceiling above
which five of its guards already refuse a command unread. Past it, the chain ends at the first guard that
blocks, which is `block-no-verify` at the head of the row, before any guard tokenizes the command.
Before, the dispatcher ran the other eight guards after that block, and the three with no ceiling that
tokenize (`block-hook-bypass`, `block-noncanonical-commit`, `block-convention-violation`) each spent about
44 s of a 70 KB run tokenizing the whole command only to add a reason. Each guard keeps its kill switch: with `block-no-verify` disabled,
`block-dangerous-git` blocks next. At or below the ceiling every guard still runs and every reason still
shows. `run-guards.test.sh` holds the row's value equal to each guard's `MAX_COMMAND_LEN`.
- The event's command is tokenized once. Six guards on the row parse the same string; the first parse is
recorded and replayed to the other five, `HOOK_SEG_*` arrays included. A parse that a guard cut short with
`exit` is not kept, and a callback's re-parse of a substring is never cached.
- hook-utils.sh: the tokenizer is linear in the command's length (see Changed).

### Changed

- hook-utils.sh: `hook::bash_parse_segments` splits a command in time linear in its length. It took one `${cmd:i:1}` per character, and bash measures the whole string on each of those, so a parse was quadratic: 1.27 s for a 10,000-character heredoc under en_US.UTF-8 against 84 ms now. The command is split in 4096- and 64-byte blocks under the C locale, and the caller's `LC_ALL` is put back afterwards. Every segment it reports is byte-identical to before under en_US.UTF-8, C.UTF-8 and C. The parse is also reachable as `hook::bash_parse_segments_uncached`, for a dispatcher that shares one parse across the hooks of an event ([#4528](https://github.com/melodic-software/claude-code-plugins/issues/4528)).

## [0.38.0] - 2026-09-27

### Changed
Expand Down
Loading