A toolbox of reusable mise lint task scripts. Pick the ones you need — each task is independent and can be adopted on its own.
Available tasks:
| Task | Tool |
|---|---|
lint:super-linter |
Super-Linter |
lint:links |
lychee |
lint:renovate-deps |
Renovate dependency tracking |
Flint relies on two tools that each play a distinct role:
mise is a polyglot dev tool manager and task runner. In the context of flint, mise serves two purposes:
-
Installing tools. mise's
[tools]section pins exact versions of the linters each task needs (e.g.,lychee,node,"npm:renovate"). Runningmise installgives every developer and CI runner the same versions, so local runs are consistent with CI. -
Running tasks. mise downloads task scripts from this repository via HTTP, wires them into your project as local commands (
mise run lint,mise run fix), and passes flags and environment variables through to each script. You don't need to clone flint — mise fetches the scripts directly from GitHub URLs pinned in yourmise.toml.
Renovate is an automated dependency update bot.
Extending the flint Renovate preset
(default.json) is essential for any repository that uses flint — without it,
SHA-pinned flint URLs and _VERSION variables in mise.toml would never get
updated. The preset ships custom managers that detect these patterns and open
PRs to bump both flint itself and the tools it runs
(e.g., Super-Linter, lychee).
Optionally, the lint:renovate-deps task adds a second
layer: it runs Renovate locally to detect which dependencies Renovate is
tracking, compares this against a committed snapshot, and fails if they
diverge — catching cases where a dependency silently falls off Renovate's
radar.
main.
The main branch may contain breaking changes.
See CHANGELOG.md for version history.
Add whichever tasks you need as HTTP remote tasks in your mise.toml,
pinned to the commit SHA of a release tag with a version comment:
# Pick the tasks you need from flint (https://github.com/grafana/flint)
[tasks."lint:super-linter"]
description = "Run Super-Linter on the repository"
file = "https://raw.githubusercontent.com/grafana/flint/8a39d96e17327c54974623b252eb5260d67ed68a/tasks/lint/super-linter.sh" # v0.9.1
[tasks."lint:links"]
description = "Check for broken links in changed files + all local links"
file = "https://raw.githubusercontent.com/grafana/flint/8a39d96e17327c54974623b252eb5260d67ed68a/tasks/lint/links.sh" # v0.9.1
[tasks."lint:renovate-deps"]
description = "Verify renovate-tracked-deps.json is up to date"
file = "https://raw.githubusercontent.com/grafana/flint/8a39d96e17327c54974623b252eb5260d67ed68a/tasks/lint/renovate-deps.py" # v0.9.1The SHA pin ensures the URL is immutable (tag-based URLs can change
if a tag is force-pushed), and the # v0.3.0 comment tells Renovate
which version is currently pinned.
Then wire up top-level lint and fix tasks that reference whichever tasks
you adopted (add any project-specific subtasks to the depends list):
[tasks."lint:fast"]
description = "Run fast lints (no Renovate)"
depends = ["lint:super-linter", "lint:links"]
[tasks.lint]
description = "Run all lints"
depends = ["lint:fast", "lint:renovate-deps"]
[tasks.fix]
description = "Auto-fix lint issues and regenerate tracked deps"
run = "AUTOFIX=true mise run lint"
[tasks.native-lint]
description = "Run lints natively (no container)"
run = "NATIVE=true mise run lint:fast"Finally, extend the flint Renovate preset
in your renovate.json5 to keep flint and its tools up to date:
{
extends: ["github>grafana/flint"],
}Without this, SHA-pinned flint URLs and tool versions (e.g.,
SUPER_LINTER_VERSION) in mise.toml will never receive automated
updates.
See grafana/docker-otel-lgtm for a real-world example of a repository using flint. Its CONTRIBUTING.md describes the developer workflow, and its mise.toml shows how the tasks are wired up.
Runs Super-Linter via Docker or Podman. Auto-detects the container runtime (prefers Podman, falls back to Docker) and handles SELinux bind-mount flags on Fedora.
mise fetches this script from the SHA-pinned URL in mise.toml
and runs it as mise run lint:super-linter. The
SUPER_LINTER_VERSION environment variable (set in mise.toml)
controls which Super-Linter image is pulled. Renovate, via the
flint preset, opens PRs to bump both the flint script URL and the
SUPER_LINTER_VERSION value when new versions are available.
Slim vs full image: Super-Linter publishes a slim image
(slim-v8.4.0) that is ~2 GB smaller than the full image. The slim
image excludes Rust, .NET/C#, PowerShell, and ARM template linters.
Flint defaults to the slim image. To use the full image instead, set
SUPER_LINTER_VERSION to the non-prefixed tag (e.g.
v8.4.0@sha256:...) and update the Renovate depName comment
accordingly (drop the versioning override so Renovate uses standard
Docker versioning).
Flags:
| Flag | Description |
|---|---|
--autofix |
Enable autofix mode (enables FIX_* vars from the env file) |
--native |
Run linters natively instead of via container |
--full |
Lint all files instead of only changed files |
--autofix and --native can also be set via the AUTOFIX=true
and NATIVE=true environment variables respectively. This is how
the fix and native-lint meta-tasks propagate them through the
depends chain.
When autofix is not enabled, all FIX_* lines are filtered out of
the env file before running Super-Linter.
Native mode:
The --native flag runs a subset of linters directly on
the host for fast local feedback. It is not a full replacement
for the Super-Linter container — CI should always use the
container for comprehensive checks.
Native mode reads the same super-linter.env file and follows
Super-Linter's default logic for determining which linters are
enabled: if any VALIDATE_*=true is set, only those linters run;
otherwise all linters run unless explicitly VALIDATE_*=false.
FILTER_REGEX_EXCLUDE is respected. FIX_* variables are honored
when --autofix is also set.
Supported native linters (subset of super-linter):
actionlintbiomecodespelleditorconfig-checkergolangci-linthadolintmarkdownlintprettierruffshellcheckshfmt
Tools must be installed separately (e.g., via
mise run setup:native-lint-tools). Missing tools and unsupported
VALIDATE_* flags are skipped with a warning. Linter configs must
be at standard project-root locations (not .github/linters/).
Environment variables:
| Variable | Default | Required | Description |
|---|---|---|---|
SUPER_LINTER_VERSION |
— | yes | Super-Linter image tag (e.g. slim-v8.4.0@sha256:... for slim, v8.4.0@sha256:... for full) |
SUPER_LINTER_ENV_FILE |
.github/config/super-linter.env |
no | Path to the Super-Linter env file |
Checks links with lychee. By default, it runs two checks: all links (local + remote) in modified files and local file links in all files. This keeps CI fast while catching both broken remote links in changed content and broken internal links across the whole repository.
mise fetches this script and runs it as mise run lint:links.
Lychee is installed via mise's [tools] section — add
lychee = "<version>" to your mise.toml. Renovate, via the
flint preset, opens PRs to bump the flint script URL when a new
version is available.
Flags:
| Flag | Description |
|---|---|
--full |
Check all links (local + remote) in all files (single run) |
--base <ref> |
Base branch to compare against (default: origin/$GITHUB_BASE_REF or origin/main) |
--head <ref> |
Head commit to compare against (default: $GITHUB_HEAD_SHA or HEAD) |
--lychee-args <args> |
Extra arguments to pass to lychee |
<file>... |
Files to check (default: .; only used with --full) |
When running in default mode, if a config change is detected
(matching LYCHEE_CONFIG_CHANGE_PATTERN or lychee-related changes
in mise.toml), the script falls back to --full behavior.
Changes to mise.toml are content-aware: only lychee-related
lines (e.g. version or task config) trigger a full check, not
unrelated tool version bumps.
GitHub URL remaps:
When running on a PR branch, the script automatically remaps GitHub
/blob/<base-branch>/ and /tree/<base-branch>/ URLs so that links
to the base branch resolve against the PR branch instead. This
ensures that links like /blob/main/README.md don't break when
the file was added or moved in the PR.
For /blob/ URLs, four ordered remap rules are applied
(lychee uses first-match-wins):
- Line-number anchors (
#L123,#L10-L20): GitHub renders these with JavaScript, so lychee can never verify the fragment. The anchor is stripped and the file is checked on the PR branch. - Scroll to Text Fragment anchors (
#:~:text=...): Browser-only feature, not present in static HTML. The anchor is stripped and the file is checked on the PR branch. - Other fragment URLs (
#section): Remapped toraw.githubusercontent.comwhere lychee can verify the fragment in the raw file content (workaround for lychee#1729). - Non-fragment URLs: Remapped from the base branch to the PR branch (the original behavior).
For /tree/ URLs, rules 1 and 4 apply (no raw remap needed).
Global GitHub URL handling:
In addition to the PR-specific remaps above, the script handles two patterns that affect ALL GitHub URLs (any repository):
- Line-number anchors (
#L123,#L10-L20): Stripped from any GitHub/blob/URL. The file is still checked, but the JS-rendered line-number fragment is skipped. This means consuming repos don't need to exclude these in theirlychee.toml. - Scroll to Text Fragment anchors (
#:~:text=...): Stripped from any GitHub/blob/URL. These are a browser-only feature not present in static HTML. - Issue comment anchors (
#issuecomment-*): The fragment is stripped so the issue/PR page is still checked, but the JS-rendered comment anchor is skipped.
Set LYCHEE_SKIP_GITHUB_REMAPS=true to disable all GitHub-specific
remaps as an escape hatch if they cause unexpected behavior.
Lychee config cleanup:
When adopting lint:links, you can remove the following entries
from your lychee.toml because flint handles them at runtime
via --remap arguments:
- GitHub blob/fragment remap for
lychee#1729
— flint remaps fragment URLs to
raw.githubusercontent.comfor the current PR's head branch, and strips line-number and Scroll to Text Fragment anchors globally. #issuecomment-*excludes — flint strips the fragment via remap so the issue/PR page is still checked.#L\d+/#L\d+-L\d+line-number excludes — flint strips the fragment via remap so the file is still checked.#:~:text=...Scroll to Text Fragment excludes — flint strips the fragment via remap so the file is still checked.
Note: flint uses --remap (not --exclude) for these because
lychee's CLI --exclude flags override config file excludes
rather than merging with them.
Environment variables:
| Variable | Default | Description |
|---|---|---|
LYCHEE_CONFIG |
.github/config/lychee.toml |
Path to the lychee config file |
LYCHEE_CONFIG_CHANGE_PATTERN |
^(\.github/config/lychee\.toml|\.mise/tasks/lint/.*)$ |
Files whose change triggers a full link check (mise.toml checked separately) |
LYCHEE_SKIP_GITHUB_REMAPS |
unset | Set to true to disable all GitHub URL remaps |
Examples:
mise run lint:links # All links in modified + local links in all files (default)
mise run lint:links --full # All links in all filesVerifies .github/renovate-tracked-deps.json is up to date by
running Renovate locally and parsing its debug logs.
mise fetches this script and runs it as mise run lint:renovate-deps.
The Renovate CLI is installed via mise's [tools] section — add
node = "<version>" and "npm:renovate" = "<version>" to your
mise.toml. Renovate plays a dual role here: the flint preset
keeps the script URL up to date, while the script itself runs Renovate
locally in --platform=local mode to discover which dependencies
Renovate is tracking and compares them against a committed snapshot.
Flags:
| Flag | Description |
|---|---|
--autofix |
Automatically regenerate and update the committed file |
Environment variables:
| Variable | Default | Description |
|---|---|---|
RENOVATE_TRACKED_DEPS_EXCLUDE |
unset | Comma-separated Renovate managers to exclude (e.g. github-actions,github-runners) |
Renovate silently stops tracking a dependency when it can no longer parse the version reference (typo in a comment annotation, unsupported syntax, moved file, etc.). When that happens, the dependency freezes in place with no PR and no dashboard entry — it simply disappears from Renovate's radar.
The Dependency Dashboard catches known dependencies that are pending or in error, but it cannot show you a dependency that Renovate no longer sees at all. This linter closes that gap by keeping a committed snapshot of every dependency Renovate tracks and failing CI when the two diverge.
The lint:renovate-deps task runs Renovate locally in
--platform=local mode, parses its debug log for the
packageFiles with updates message, and generates a dependency
list (grouped by file and manager). It then diffs this against the
committed .github/renovate-tracked-deps.json:
- If they match → linter passes
- If they differ → linter fails with a unified diff showing which dependencies were added or removed
- With
--autofixflag (orAUTOFIX=trueenv var) → automatically regenerates and updates the committed file
-
A dependency disappears (e.g., someone removes a
# renovate:comment or changes a file that Renovate was matching) → CI fails, showing the removed dependency in the diff. The author can then decide whether the removal was intentional or accidental. -
A new dependency is added → CI fails because the committed snapshot is stale. Run
mise run fix(orAUTOFIX=true mise run lint:renovate-deps) to regenerate and update the file, then commit. -
Routine regeneration → After any change to
renovate.json5, Dockerfiles,go.mod,package.json, or other files Renovate scans, the linter will detect the change and require regeneration.
lint:super-linter accepts --autofix and --native flags.
Both can also be set as environment variables (AUTOFIX=true,
NATIVE=true), which is how the fix and native-lint
meta-tasks propagate them — mise's depends cannot forward CLI
flags, but env vars flow through naturally. Tasks that don't
recognize these variables simply ignore them.
Check mode (default):
mise run lint # Check all linters, fail on issues
mise run lint:super-linter # Check code style, fail on issues
mise run lint:renovate-deps # Verify tracked deps, fail if out of dateFix mode:
mise run fix # Auto-fix all fixable issues
# Or run individual linters:
mise run lint:super-linter --autofix # Apply code fixes
mise run lint:renovate-deps --autofix # Regenerate tracked depsLinters that don't support autofix (like lychee link checker)
silently ignore the AUTOFIX environment variable.
Native mode:
mise run native-lint # Fast lints, natively (no container)
# Or run directly:
NATIVE=true mise run lint:fast # Same effect
mise run lint:super-linter --native # Single task with CLI flagNative mode is useful in environments where Docker/Podman is
unavailable (e.g., inside containers, CI hooks). native-lint
targets lint:fast (super-linter + links), skipping
lint:renovate-deps which requires the Renovate CLI. Tasks
that don't use a container (like lint:links) ignore the
NATIVE variable.
Flint provides a pre-commit task that runs native linters on
every commit — fast feedback without the container overhead. To
set it up:
mise run setup:pre-commit-hookThis generates a .git/hooks/pre-commit that runs
mise run pre-commit, which uses native mode for fast checks
without requiring a container.
For consuming repos, add these tasks to your mise.toml:
[tasks.pre-commit]
description = "Pre-commit hook: native lint"
depends = ["setup:native-lint-tools"]
run = "NATIVE=true mise run lint:fast"
[tasks."setup:pre-commit-hook"]
description = "Install git pre-commit hook"
run = "mise generate git-pre-commit --write --task=pre-commit"Then run mise run setup:pre-commit-hook once per clone.
Flint provides a Renovate shareable preset with custom managers that automatically update:
- SHA-pinned flint versions in
mise.toml(raw.githubusercontent.comURLs with commit SHA and version comment) _VERSIONvariables inmise.toml(e.g.,SUPER_LINTER_VERSION)
Add this to your renovate.json5:
{
extends: ["github>grafana/flint"],
}Each task expects certain config files that your repository must provide. You only need the files for the tasks you adopt:
lint:super-linter— Super-Linter env file (.github/config/super-linter.env) to select which validators to enable and whichFIX_*vars to set, plus any linter config files (.golangci.yaml,.markdownlint.yaml,.yaml-lint.yml,.editorconfig, etc.)lint:links— Lychee config (.github/config/lychee.toml) for exclusions, timeouts, remappingslint:renovate-deps— Renovate config (.github/renovate.json5) and committed snapshot (.github/renovate-tracked-deps.json)- Renovate preset — Add
"github>grafana/flint"to yourrenovate.json5extendsarray to enable automatic updates of flint URLs and tool versions
This project uses Semantic Versioning. Breaking changes will be documented in CHANGELOG.md and will result in a major version bump.
Always pin to a specific commit SHA in your mise.toml file
URLs with a version comment (e.g., # v0.6.0). Never reference
main directly as it may contain unreleased breaking changes. To
find the commit SHA for a release tag, run
git rev-parse v0.6.0.
See RELEASING.md.