1 when a limit is crossed.
mehen.toml
File discovery
mehen looks formehen.toml (preferred) or .mehen.toml, starting in the current working
directory and walking up to the enclosing git repository root — the repository is the boundary,
so a config above it (for example a stray ~/mehen.toml) can never leak into a project. Outside
a git repository, only the current directory is checked. The first file found wins, so a config
at the repository root applies from any subdirectory. When both names exist in the same
directory, mehen.toml is used and a warning is logged.
The global --config <PATH> flag bypasses discovery and pins an explicit file for any command:
--config file is an error (exit 1) — a requested gate is never silently
skipped.
Threshold limits
Each entry under[thresholds] maps a metric name to a numeric limit. The dotted spelling
(loc.lloc = 500) and the quoted spelling ("loc.lloc" = 500) are equivalent.
The limit’s meaning follows the metric’s polarity:
A value exactly at the limit passes. mehen knows the direction for every metric it publishes — the
same polarity rules that drive
mehen top-offenders ranking
and diff trend arrows.
Metric names are the same ones --metric accepts — the relationship goes both ways, so any
configurable key is also selectable as a diff/top-offenders column: the source-code families
(cognitive, cyclomatic, loc.*, halstead.*, mi.*, abc, nargs, nexit, nom.*,
npa.*, npm.*, wmc, including aggregate spellings such as cognitive.max or
nom.functions.max), the fixed history family (history.hotspot,
history.churn.relative, …), and the namespaced SQL and
Markdown families (sql.*, markdown.*). Names that no analyzer
publishes — a bare mi, an invented cognitive.maximum — are rejected when the config loads.
Per-language overrides
A[languages.<lang>.thresholds] table overrides the global limit for files of that language,
metric by metric. Language identifiers accept the usual aliases (python/py,
typescript/ts, ruby/rb, …); two sections that name the same language through different
aliases are rejected at load time.
mehen.toml
What gets gated
Thresholds apply to the metrics a command actually reports:
Two consequences worth knowing:
- A metric the analyzed file does not publish is skipped, never read as
0. Asql.change_risk_scorethreshold does not fire on Python files, and anmi.visual_studiominimum does not produce false violations on files where MI is unavailable. - In
mehen diff, deleted files have no head side to gate, and Markdown files route through the documentation pipeline with its own--fail-ongates.
mehen metrics and mehen diff fail with exit 1 through their existing analysis-error
contract, while mehen top-offenders keeps its contract of skipping unparsable files from the
ranking (so a broken file cannot fabricate a passing measurement — but it does not fail the run
by itself either).
The violation report
Crossed limits print a grouped report on stderr — after the command’s normal stdout output, so JSON and Markdown reports stay complete for machine consumers — and the command exits1:
NO_COLOR unset. With
--output-format json, mehen diff additionally embeds the violations as a top-level
threshold_violations array (present only when the gate fired), so machine consumers can tell a
quality-gate exit from an analysis failure.
Validation
The configuration is validated when it loads. A metric name no analyzer publishes — a typo, a bare family root likemi (the analyzers publish mi.visual_studio, mi.original, mi.sei),
an invented aggregate like cognitive.maximum, or a misspelled history.* key — fails
immediately with a pointer into the file, as do unknown languages, non-numeric limits, and two
spellings of the same metric in one table (cognitive and cognitive.sum are one logical
threshold; contradictory duplicates would silently disable the stricter gate):
1 before any analysis runs. Namespaced sql.* and markdown.*
names validate against the owning analyzer’s published catalogue too (including the dynamic
sql.statement.kind_count.<kind> and sql.dialect.is_<dialect> families), so
sql.modularit_health is caught just like cognitve.
Relationship to other gates
The gates are complementary: the Action input limits how much a PR may move a metric, while
mehen.toml limits where the metric may be. A repository that adopts a mehen.toml gets the
same gating locally, in pre-commit hooks, and in any CI system — not only in the GitHub Action.
When the gate fires inside the Action, the PR comment and the report_json / report_markdown
outputs still publish with the complete report before the workflow step fails.
See also
- Commands overview — the three commands that enforce thresholds.
- Concepts → Thresholds and diffs — gating strategies in CI.
- Code metrics — the metric names you can threshold.