Skip to content

command

Shell out to an external CLI per matched file. Exit 0 is a pass; non-zero is one violation whose message is the (truncated) stdout+stderr. Working directory is the repo root; stdin is closed.

Argv tokens accept the same path-template substitutions as pair and for_each_dir: {path}, {dir}, {stem}, {ext}, {basename}, {parent_name}. The first token is the program (looked up via PATH if it's a bare name).

Environment threaded into the child:

VarValue
ALINT_PATHmatched path (relative to root)
ALINT_ROOTabsolute repo root
ALINT_RULE_IDthe rule's id:
ALINT_LEVELerror / warning / info
ALINT_VAR_<NAME>one per top-level vars: entry
ALINT_FACT_<NAME>one per resolved fact, stringified

timeout: <seconds> (default 30) bounds each invocation; past the limit the child is killed and a violation reports the timeout.

Output is drained while the child runs (a child writing more than a pipe buffer cannot deadlock), and after the child exits alint waits at most 2 seconds for descendants still holding its output pipes. What the timeout kills depends on whether alint's stdin is a terminal. In a non-interactive run (CI, the language server, piped stdin) each spawned process (the command rule, generated_file_fresh, command_idempotent, the command fix, custom: facts) gets its own process group on Unix: the timeout kills the whole group, grandchildren included, and descendants left holding the output pipes after a normal exit are killed too. In an interactive run (stdin is a terminal) the child stays in alint's process group so Ctrl-C reaches it and it can prompt on /dev/tty; the timeout then kills only the direct child. A timeout too large to represent means no deadline. On Windows only the direct child is killed.

Trust gate. Every process-spawning rule kind — command, generated_file_fresh, and command_idempotent — is allowed only in the user's own top-level config. Any of them introduced via extends: (local file, HTTPS URL, or alint://bundled/) is a load-time error — the same gate that protects custom: facts. Adopting a published ruleset must never imply granting it arbitrary code execution.

Path confinement + allow_out_of_root. Every config-declared path is confined to the repo root — a rule can't read or resolve a file outside the tree it was pointed at. The top-level-only allow_out_of_root: key relaxes this for reads (json_schema_passes schema_path:, pair_hash target:, registry_paths_resolve source:) when a trusted config must reference an external file. It is rejected from extends:'d rulesets (same trust model as the spawn gate above) and a permitted read emits a note. See Configuration → allow_out_of_root.

--changed interaction: command is a per-file rule, so under alint check --changed it spawns only for files in the diff. The expensive check is automatically incremental in CI.


OptionTypeRequiredDefaultDescription
commandlist of stringyesArgv tokens. The first token is the program (looked up via PATH if it's a bare name); remaining tokens accept {path} and friends.
timeoutinteger (>= 1)nullPer-file timeout in seconds. Default 30. Past this, the child is killed and a violation reports the timeout.

Plus the common paths, level, id, when, and expect_matches fields. This table is generated from the JSON Schema; option types and defaults are authoritative.

The rule fires on this repository:

  • src/
  • src/a.rs

    ✗ error no-todo-markers

    `sh` failed (exit 1): 1:// TODO: drop the debug hook before release

  • src/b.rs✓
src/a.rs
// TODO: drop the debug hook before release
fn a() {}
src/b.rs
fn b() {}

With this .alint.yml:

version: 1
rules:
- id: no-todo-markers
kind: command
paths: "src/**/*.rs"
command: ["sh", "-c", "if grep -n TODO \"{path}\"; then exit 1; fi"]
level: error

alint check reports:

Terminal window
--- src/a.rs -------------------------------------------------------------------
x error no-todo-markers
`sh` failed (exit 1):
1:// TODO: drop the debug hook before release
Summary (1 violation):
x 1 error
0 passing * 1 failing

This repository is compliant:

  • src/
  • src/lib.rs✓
  • src/main.rs✓
src/lib.rs
pub fn ok() {}
src/main.rs
fn main() {}

With this .alint.yml:

version: 1
rules:
- id: no-todo-markers
kind: command
paths: "src/**/*.rs"
command: ["sh", "-c", "if grep -n TODO \"{path}\"; then exit 1; fi"]
level: error

alint check reports:

Terminal window
v All 1 rule(s) passed.