Dynamic rules
Validation rules without code changes
On this page
Overview
The rule engine lets you define validation behavior through TOML files. Rules can block operations based on patterns (repository, branch, file, command), warn about risky actions, or allow operations that built-in validators would normally block.
The engine auto-detects glob and regex patterns, evaluates rules by priority (highest first), merges project config over global config, and stops on the first matching rule by default.
Quick start
Create a rule file at .klaudiush/config.toml in your project root. Here's
a rule that blocks direct pushes to main:
[rules]
enabled = true
[[rules.rules]]
name = "block-main-push"
description = "Block direct pushes to main"
priority = 100
[rules.rules.match]
validator_type = "git.push"
branch_pattern = "main"
remote = "origin"
[rules.rules.action]
type = "block"
message = "Direct push to main is not allowed. Use a pull request." Global rules go in $XDG_CONFIG_HOME/klaudiush/config.toml (default ~/.config/klaudiush/config.toml) and apply to all projects. Project rules
override global rules when they share the same name.
# $XDG_CONFIG_HOME/klaudiush/config.toml
[[rules.rules]]
name = "warn-force-push"
description = "Flag force pushes"
priority = 50
[rules.rules.match]
validator_type = "git.push"
command_pattern = "*--force*"
[rules.rules.action]
type = "warn"
message = "Force push detected. Check that you have the latest changes." Verify your rules load with debug logging:
klaudiush --debug Rule configuration
Each rule has a unique name, optional priority, match conditions, and an action. All match conditions use AND logic - every non-empty condition must match for the rule to fire.
[[rules.rules]]
name = "my-rule" # unique identifier
description = "What this rule does"
enabled = true # default: true
priority = 100 # higher = evaluated first
[rules.rules.match]
# all non-empty conditions must match (AND logic)
validator_type = "git.push"
branch_pattern = "main"
[rules.rules.action]
type = "block" # block | warn | allow
message = "Reason for the block" Priority
Rules evaluate from highest priority to lowest. When stop_on_first_match is
true (the default), the first matching rule wins.
Pattern matching
The engine detects glob vs regex automatically. If the pattern contains anchors
(^, $), character classes ([), or quantifiers
(+, .*), it's treated as regex. Otherwise, it's a glob.
Glob patterns
# Match any file in docs directory
file_pattern = "docs/*"
# Match markdown files in any subdirectory
file_pattern = "**/*.md"
# Match feature branches
branch_pattern = "feat/*" Regex patterns
# Match semantic version branches
branch_pattern = "^release/v[0-9]+\\.[0-9]+$"
# Match terraform files
file_pattern = ".*\\.(tf|tfvars)$" Match conditions
Combine these conditions freely. Omitted conditions match everything.
| Condition | Description |
|---|---|
validator_type | Filter by validator (git.push, git.*, *) |
repo_pattern | Match repository root path |
branch_pattern | Match branch name |
file_pattern | Match file path |
command_pattern | Match bash command |
content_pattern | Match file content (always regex) |
tool_type | Match hook tool type (Bash, Write, etc.) |
event_type | Match hook event (PreToolUse) |
Actions
Three action types determine what happens when a rule matches:
# Block - stops the operation
[rules.rules.action]
type = "block"
message = "This operation is not allowed"
# Warn - logs a warning, operation proceeds
[rules.rules.action]
type = "warn"
message = "This operation might cause issues"
# Allow - skips further rules and built-in validation
[rules.rules.action]
type = "allow" allow rules skip validation
An allow action bypasses built-in validators entirely. Use it for test
fixtures and trusted paths, not as a general escape hatch.
Configuration precedence
Rules load from multiple sources, highest priority first:
- CLI flags
- Environment variables (
KLAUDIUSH_*) - Project config (
.klaudiush/config.toml) - Global config (
$XDG_CONFIG_HOME/klaudiush/config.toml) - Built-in defaults
Rules with the same name merge (project wins). Rules with different names combine into one list.
Validator types
| Type | Description |
|---|---|
git.push | Push operations |
git.commit | Commit operations |
git.add | Add operations |
git.pr | Pull request operations |
file.* | All file validators (markdown, shell, terraform, etc.) |
secrets.secrets | Secret detection |
* | All validators |
Examples
Allow secrets in test files
[[rules.rules]]
name = "allow-test-secrets"
description = "Allow secrets in test fixtures"
priority = 1000
[rules.rules.match]
validator_type = "secrets.secrets"
file_pattern = "**/test/**"
[rules.rules.action]
type = "allow" Require ticket reference for commits to main
[[rules.rules]]
name = "require-ticket-main"
description = "Require JIRA ticket in commits to main"
priority = 100
[rules.rules.match]
validator_type = "git.commit"
branch_pattern = "main"
[rules.rules.action]
type = "block"
message = "Commits to main must reference a JIRA ticket (e.g., JIRA-123)" Block origin push in org repos
[[rules.rules]]
name = "block-org-origin"
description = "Block push to origin in org repos"
priority = 100
[rules.rules.match]
validator_type = "git.push"
repo_pattern = "**/myorg/**"
remote = "origin"
[rules.rules.action]
type = "block"
message = "Organization repos use 'upstream'. Push to your fork." Exceptions integration
Rules that block operations can support exception bypasses. Add a reference field
to the action, then configure an exception policy for that reference.
# Add a reference to enable exception bypass
[rules.rules.action]
type = "block"
message = "Direct push to main is not allowed"
reference = "RULE001"
# Then configure the exception policy
[exceptions.policies.RULE001]
enabled = true
allow_exception = true
require_reason = true