Quick start¶
The ryl check subcommand¶
ryl's CLI is moving to subcommands: ryl check is the lint pass (a dedicated
ryl format formatter is coming). ryl check <paths> is the recommended form and
is used throughout these docs. Bare ryl <paths> still lints identically today, but
it is being phased out — a future release will warn on it and a later one will remove
it, so adopt ryl check now.
Run a lint¶
Point ryl at a file or directory:
# Lint a single file
ryl check path/to/file.yaml
# Lint a project (recursively scans .yml/.yaml, honouring .gitignore)
ryl check .
ryl does not enable any rules by default, so these commands report no
configuration found (exit 2) until a configuration enables at least one rule.
To lint with yamllint's standard rule set straight away, pass it inline:
or drop a config in your project (see Configure for your project below).
Lint from stdin¶
Pass - as the input to read YAML from standard input — useful for
editor integrations where the buffer is not yet on disk:
cat file.yaml | ryl check -
# Provide a filename so diagnostics, config discovery, and
# yaml-files / per-file-ignores match the right path:
cat file.yaml | ryl check - --stdin-filename path/to/file.yaml
Without --stdin-filename, diagnostics are labelled <stdin>, config
discovery is anchored at the current working directory, and all
path-based filtering (yaml-files, per-file-ignores, per-rule ignore
patterns) is skipped so every enabled rule runs. - cannot be combined
with other inputs, and --fix cannot read from stdin (use --diff to
preview fixes instead).
Exit codes:
0— no problems found.1— lint errors, invalid YAML, or a path that could not be read (including nonexistent files).2— CLI usage error (no inputs provided, bad flags), or--strictwas set and only warnings were produced.
ryl never enables a rule unless a configuration explicitly turns it on, so two
cases exit 2 rather than silently linting nothing:
- No configuration found anywhere (no
-c/-d, noYAMLLINT_CONFIG_FILE, no discovered.ryl.toml/.yamllint). Create a config that enables rules, or pass a YAML config withextends: defaultfor yamllint's standard rule set. - A configuration that enables no rules (
rules: {}, an empty[rules]/[tool.ryl], or one disabling everything). Enable at least one rule, or useextends: default.
This is stricter than yamllint, which lints with the default preset when no config
is found and silently accepts a rule-less config. Give ryl a config containing
extends: default to reproduce yamllint's out-of-the-box behaviour.
Apply auto-fixes¶
ryl can automatically fix a subset of rules:
See the Rules reference for which rules are fixable.
--fix rewrites files in place but never writes through a symlink: a
symlinked input is linted but skipped for fixing (with a warning on
stderr), so a symlink in an untrusted tree cannot redirect a write to a
file outside it. This mirrors directory scanning, which does not follow
symlinks.
Preview fixes as a diff¶
--diff runs the same safe fixes as --fix but, instead of writing,
prints a unified diff (3 lines of context) of what would change to
stdout — modelled on ruff check --diff:
This is handy for CI previews, PR review, and parallel-safe runners such
as hk that apply the diff themselves rather than
re-invoking the linter. --diff never modifies files, is mutually
exclusive with --fix, and (unlike --fix) works with -/stdin.
Like ruff check --diff, the exit code reflects only the diff —
remaining unfixable findings are neither printed nor counted:
1— at least one file would change.0— no file would change.2— CLI usage error.
A file that cannot be parsed (or a symlink) is skipped with a notice on
stderr and does not affect the exit code. A non-UTF-8 or BOM-prefixed file
is also skipped: a textual diff of its decoded content could not be applied
back to the original bytes, so use --fix (which preserves the encoding)
for those. For embedded YAML in Markdown, the diff is reported at the
host-file level (one diff per .md).
Configure for your project¶
The recommended TOML config is deliberately explicit and local: it has
no default-on rules and no extends/inheritance, so a single .ryl.toml
(discovered by searching upward, and preferred over a .yamllint) is the
entire ruleset for the files beneath it; a monorepo can have many, each
governing its subtree. A yamllint-style YAML config instead keeps yamllint
semantics, where extends: merges in a preset or another file. When no
project config is found, ryl falls back to a single user-global config (see
below). Either way there are no default-on rules, so a config that enables
nothing exits 2 rather than silently linting nothing.
Drop a .ryl.toml (or ryl.toml) at the root of your repo. TOML
configuration is flat — copy the preset you want from
Configuration presets and customise from there:
[files]
yaml = [
"*.yaml",
"*.yml",
".yamllint",
]
# ... rule enable/disable table from the preset ...
[rules.line-length]
max = 120
allow-non-breakable-words = true
YAML configuration is also accepted for parity with yamllint and supports
extends: for selecting a preset. Both .yamllint and .ryl.toml are
discovered automatically. TOML is the recommended format for ryl-specific
features (such as fix selection) that have no upstream yamllint
equivalent.
To keep config out of the project root, ryl also discovers a ryl-native TOML
config inside a repo-local .config/ directory (the RuboCop/rumdl convention).
At each directory in the upward search the candidates are tried in this order,
and the first match wins:
.ryl.tomlryl.toml.config/.ryl.toml.config/ryl.tomlpyproject.toml(only when it has a[tool.ryl]table)
.config/ holds ryl-native TOML only: the legacy .yamllint/.yamllint.yaml/
.yamllint.yml files are discovered at the directory level, never inside
.config/. A .config/ryl.toml is a true drop-in for a root ryl.toml: its
[files]/ignore globs and relative ignore-from-file paths resolve against
the project root (the directory containing .config/), not .config/ itself.
If you already have a yamllint configuration, use the built-in converter:
See Migrating from yamllint for details.
Configure across projects (user-global)¶
When no project config is found, ryl falls back to a user-global config so you
can set personal defaults once. It reads its own TOML config first —
<config-dir>/ryl/.ryl.toml (or ryl.toml), following the ruff/Biome
convention where <config-dir> is $XDG_CONFIG_HOME if set, else the
platform-native config dir (~/.config/ryl on Linux, ~/Library/Application
Support/ryl on macOS, %APPDATA%\ryl on Windows) — then falls back to
yamllint's <config-dir>/yamllint/config for compatibility. A project config,
-c/-d, or YAMLLINT_CONFIG_FILE all take precedence over the user-global
config. YAMLLINT_CONFIG_FILE accepts only a yamllint YAML config (pointing it
at a .toml errors); use -c/-d or project discovery for ryl-native TOML.
If you have a yamllint user-global config, ryl --migrate-user-config
--migrate-write converts it to the ryl-native ryl.toml (see Migrating from
yamllint). Migration is optional, since ryl also
reads the yamllint location directly.
Configuration precedence¶
ryl resolves the configuration governing each file it lints, trying these
sources in order and stopping at the first hit. -d/-c and
YAMLLINT_CONFIG_FILE pin a single config for the whole run; otherwise project
discovery runs per file, so a monorepo can hold many .ryl.toml files, each
governing its own subtree. The winning config must enable at least one rule, or
ryl exits 2:
flowchart TD
Start([resolve config]) --> D{"-d / --config-data?"}
D -->|yes| UseInline["use inline YAML"] --> Done([config resolved])
D -->|no| C{"-c / --config-file?"}
C -->|yes| UseFile["load file: TOML or YAML by extension"] --> Done
C -->|no| P{"project config?<br/>walk up from inputs to HOME"}
P -->|"TOML up-tree"| UseProjToml["nearest TOML:<br/>.ryl.toml > ryl.toml<br/>> .config/.ryl.toml > .config/ryl.toml<br/>> pyproject.toml [tool.ryl]"] --> Done
P -->|"else .yamllint up-tree"| UseProjYaml["nearest .yamllint /<br/>.yamllint.yaml / .yamllint.yml"] --> Done
P -->|none| E{"YAMLLINT_CONFIG_FILE set?"}
E -->|"points at .toml"| Err1["error: use -c / project discovery<br/>for ryl TOML (exit 2)"]
E -->|"YAML and exists"| UseEnv["load as yamllint YAML"] --> Done
E -->|"missing or unset"| G{"user-global config?"}
G -->|"ryl TOML"| UseRyl["config-dir/ryl/.ryl.toml > ryl.toml"] --> Done
G -->|"else yamllint YAML"| UseYl["config-dir/yamllint/config"] --> Done
G -->|none| Err2["error: no configuration found (exit 2)"]
Done --> R{"any rule enabled?"}
R -->|yes| OK([lint])
R -->|no| Err3["error: no rules enabled (exit 2)"]
classDef default stroke-width:3px;
linkStyle default stroke-width:3px;