Built-in Filters
See also docs/contributing/TECHNICAL.md for the full architecture overview
Each .toml file in this directory defines one filter and its inline tests.
Files are concatenated alphabetically by build.rs into a single TOML blob embedded in the binary.
When to Use a TOML Filter
TOML filters strip noise lines — they don't reformat output. The filtered result must still look like real command output (see Design Philosophy). For the full TOML-vs-Rust decision criteria, see CONTRIBUTING.md.
TOML works well for commands with predictable, line-by-line text output where regex filtering achieves 60%+ savings:
- Install/update logs (brew, composer, poetry) — strip
Using .../Already installedlines - System monitoring (df, ps, systemctl) — keep essential rows, drop headers/decorations
- Simple linters (shellcheck, yamllint, hadolint) — strip context, keep findings
- Infra tools (terraform plan, helm, rsync) — strip progress, keep summary
For the full contribution checklist (including discover/rules.rs registration), see src/cmds/README.md — Adding a New Command Filter.
Adding a filter
- Copy any existing
.tomlfile and rename it (e.g.my-tool.toml) - Update the three required fields:
description,match_command, and at least one action field - Add
[[tests.my-tool]]entries to verify the filter behaves correctly - Run
cargo test— the build step validates TOML syntax and runs inline tests
File format
[filters.my-tool]
description = "Short description of what this filter does"
match_command = "^my-tool\\b" # regex matched against the full command string
strip_ansi = true # optional: strip ANSI escape codes first
strip_lines_matching = [ # optional: drop lines matching any of these regexes
"^\\s*$",
"^noise pattern",
]
max_lines = 40 # optional: keep only the first N lines after filtering
on_empty = "my-tool: ok" # optional: message to emit when output is empty after filtering
[[tests.my-tool]]
name = "descriptive test name"
input = "raw command output here"
expected = "expected filtered output"
Available filter fields
| Field | Type | Description |
|---|---|---|
description |
string | Human-readable description |
match_command |
regex | Matches the command string (e.g. "^docker\\s+inspect") |
strip_ansi |
bool | Strip ANSI escape codes before processing |
filter_stderr |
bool | Capture and merge stderr into stdout before filtering (use for tools like liquibase that emit banners to stderr) |
strip_lines_matching |
regex[] | Drop lines matching any regex |
keep_lines_matching |
regex[] | Keep only lines matching at least one regex |
replace |
array | Regex substitutions ({ pattern, replacement }) |
match_output |
array | Short-circuit rules ({ pattern, message }) |
truncate_lines_at |
int | Truncate lines longer than N characters |
max_lines |
int | Keep only the first N lines |
tail_lines |
int | Keep only the last N lines (applied after other filters) |
on_empty |
string | Fallback message when filtered output is empty |
Naming convention
Use the command name as the filename: terraform-plan.toml, docker-inspect.toml, mix-compile.toml.
For commands with subcommands, prefer <cmd>-<subcommand>.toml over grouping multiple filters in one file.
Build and runtime pipeline
How a .toml file goes from contributor → binary → filtered output.
flowchart TD
A[["src/filters/my-tool.toml\n(new file)"]] --> B
subgraph BUILD ["cargo build"]
B["build.rs\n1. ls src/filters/*.toml\n2. sort alphabetically\n3. concat → BUILTIN_TOML"] --> C
C{"TOML valid?\nDuplicate names?"} -->|"fail"| D[["Build fails\nerror points to bad file"]]
C -->|"ok"| E[["OUT_DIR/builtin_filters.toml\n(generated)"]]
E --> F["rustc embeds via include_str!"]
F --> G[["rtk binary\nBUILTIN_TOML embedded"]]
end
subgraph TESTS ["cargo test"]
H["test_builtin_filter_count\nassert_eq!(filters.len(), N)"] -->|"wrong count"| I[["FAIL"]]
J["test_builtin_all_filters_present\nassert!(names.contains('my-tool'))"] -->|"name missing"| K[["FAIL"]]
L["test_builtin_all_filters_have_inline_tests\nassert!(tested.contains(name))"] -->|"no tests"| M[["FAIL"]]
end
subgraph RUNTIME ["rtk my-tool args"]
R["TomlFilterRegistry::load()\n1. .rtk/filters.toml\n2. ~/.config/rtk/filters.toml\n3. BUILTIN_TOML\n4. passthrough"] --> S
S{"match_command\nmatches?"} -->|"no match"| T[["exec raw (passthrough)"]]
S -->|"match"| U["exec command\ncapture stdout"]
U --> V["8-stage pipeline\nstrip_ansi → replace → match_output\n→ strip/keep_lines → truncate\n→ tail_lines → max_lines → on_empty"]
V --> W[["print filtered output + exit code"]]
end
G --> H & J & L & R
Filter lookup priority
flowchart LR
CMD["rtk my-tool args"] --> P1
P1{"1. .rtk/filters.toml\n(project-local)"}
P1 -->|"match"| WIN["apply filter"]
P1 -->|"no match"| P2
P2{"2. ~/.config/rtk/filters.toml\n(user-global)"}
P2 -->|"match"| WIN
P2 -->|"no match"| P3
P3{"3. BUILTIN_TOML\n(binary)"}
P3 -->|"match"| WIN
P3 -->|"no match"| P4[["exec raw (passthrough)"]]
First match wins. A project filter with the same name as a built-in shadows the built-in and triggers a warning:
[rtk] warning: filter 'make' is shadowing a built-in filter
Custom filters and trust
You can add your own filters in two places (both use the format above):
- Project-local —
.rtk/filters.toml(committed with a repo, applies in that project) - User-global —
~/.config/rtk/filters.toml(applies in every project)
Because a filter can rewrite or hide the command output an agent sees, custom filter files are not applied until you trust them. An untrusted (or edited) filter file is skipped silently on the command path — RTK never prints a warning around a rewritten command. You discover and enable untrusted filters through commands you run deliberately:
rtk trust # lists each detected filter (labelled project/global) + a risk summary, then asks to confirm ([y/N], or --yes)
rtk untrust # revokes trust
- Trust is recorded as a SHA-256 of the file's contents, so editing a trusted file requires re-running
rtk trust— a content change invalidates trust. rtk initdetects existing custom filters and lets you enable them — an interactive[y/N], or--trust-filters/--no-trust-filtersfor scripts. It stays silent for an empty template (comments only) and, when run non-interactively, leaves filters disabled.- Built-in filters (this directory) are compiled into the binary and always trusted; only the on-disk project and user-global files are gated.
Honest limitation: this is consent + tamper-evidence, not a sandbox. An attacker who can write your filter file can usually also write the trust store (
~/.local/share/rtk/trusted_filters.json) and bypass the gate. It defends the common case — a filter dropped in by a script, a dotfile sync, or an untrusted repo — not a same-user attacker who specifically targets RTK.