Run Linters
Execute linters after code changes are complete to ensure code quality and consistency.
Arguments
An optional path or glob may be passed to scope the run. Validate it before any use: a scope not matching ^[A-Za-z0-9._*][A-Za-z0-9._/*-]*$ is refused and reported to the user, never passed to any command. Two mechanisms carry the fence, each against its own threat: the anchored first character keeps - out of position one, and the class body keeps out $, backtick, quote, space and semicolon, so nothing the shell could expand, split or chain survives validation — but a character class cannot stop a linter's own option parser, which is why every sink also passes the scope after an end-of-options separator. When present, both the linting and the fix loop are restricted to files under it, passed as one quoted argument after -- to each linter's own path argument (npx eslint -- "<path>", rubocop -- "<path>", ruff check -- "<path>", golangci-lint run -- "<path>/...", and likewise per tool); the quoting keeps an admitted * a literal argument for the linter's own globbing rather than the shell's. The linters wrapper, which takes no path, is skipped in favour of the per-tool commands when a scope is given. When absent, the whole repository is linted as before.
When to Use
- After completing a set of code changes (not after each small edit)
- Before creating a commit or PR
- When asked to verify code quality
Step 0: Check for the linters wrapper
The linters wrapper is the preferred path, but it is not installed everywhere. Check for it first:
which linters
If linters is found: continue to Step 1 unchanged.
If linters is NOT found: tell the user plainly that the linters wrapper is not installed on this machine and that you are falling back to the repository's own linters. Then detect which linters the repository configures and run them directly:
| Config file present | Linter to run |
| --- | --- |
| .eslintrc*, eslint.config.* | npx eslint . |
| .rubocop.yml | bundle exec rubocop (or rubocop) |
| ruff.toml, .ruff.toml, pyproject.toml (with tool.ruff) | ruff check . |
| .golangci.yml, .golangci.yaml | golangci-lint run |
| Cargo.toml | cargo clippy |
| .markdownlint* | npx markdownlint-cli2 . |
| .yamllint* | yamllint . |
| *.sh files | shellcheck on the shell scripts |
Prefer the project's own runner whenever one exists — an npm run lint script in package.json, a rake lint task, or a make lint target that already wraps the linter — over invoking the binary directly. Use the package manager the repository already uses (npm, yarn, or pnpm).
If neither the linters wrapper nor any recognised linter config is found, stop and tell the user exactly which config files you searched for. Do NOT claim the code is lint-clean.
Step 1: Run Linters
Execute the linters command which auto-detects active linters in the current repository and runs them with proper configurations:
linters
Step 2: Analyze Results
An issue is any diagnostic the linter emits at severity error or warning. Diagnostics at note, info, style or convention are listed to the user and not fixed. A non-zero exit with no diagnostic lines is a failed run, not an issue.
- If no issues: Report success and proceed
- If issues found: Continue to Step 3
Step 3: Fix Issues
For each issue reported:
- Read the affected file
- Understand the linting error
- Fix the issue in the source code using Edit tool
- Re-run
lintersto verify the fix
Repeat until all issues are resolved.
Important Rules
- Do NOT run after every small change - wait until a logical set of changes is complete
- Fix all issues before reporting completion
- NEVER report lint success without a linter having actually run and produced output - a missing command, a failed invocation, or a run you skipped is not a pass
- NEVER modify linter configuration files to suppress or ignore issues
- NEVER add inline disable comments (e.g.,
// eslint-disable,# noqa,// nolint) to bypass issues - Always fix the actual code, not the linter rules
- If an issue seems impossible to fix properly, ask the user for guidance
Forbidden Files - NEVER Modify
The following configuration files must NEVER be edited to work around linting issues:
JavaScript/TypeScript:
.eslintrc,.eslintrc.js,.eslintrc.json,.eslintrc.yml.prettierrc,.prettierrc.js,.prettierrc.jsoneslint.config.js,eslint.config.mjstsconfig.json(for strict mode or type checking options)
Python:
.flake8,setup.cfg(flake8 section)pyproject.toml(tool.flake8, tool.pylint, tool.ruff sections).pylintrc,pylintrcruff.toml,.ruff.tomlmypy.ini,.mypy.ini
Ruby:
.rubocop.yml,.rubocop_todo.yml
Go:
.golangci.yml,.golangci.yaml
Rust:
clippy.toml,.clippy.tomlrustfmt.toml,.rustfmt.toml
Markdown:
.markdownlint.json,.markdownlint.yaml,.markdownlint.yml.markdownlintrc
General:
.editorconfig- Any file that defines linting rules or ignores
Forbidden Patterns - NEVER Use
Do NOT add these patterns to bypass linting:
# JavaScript/TypeScript
/* eslint-disable */
// eslint-disable-line
// eslint-disable-next-line
/* prettier-ignore */
// @ts-ignore
// @ts-nocheck
# Python
# noqa
# type: ignore
# pylint: disable
# ruff: noqa
# Go
//nolint
//nolint:all
# Ruby
# rubocop:disable
# Rust
#[allow(...)]
#![allow(...)]
If you encounter an issue that seems unfixable, explain the problem to the user and ask how they want to proceed.
Shell Script Linting Rules (SL0001, SL0002)
When fixing shell script lint errors (e.g., from shellcheck or custom shell linters), apply these rules:
SL0001: Variables must use braces
Always wrap shell variables in ${} braces. This prevents ambiguity and word-splitting bugs.
# Bad
echo "$HOME/.local/bin:$PATH"
if [ "$CURRENT_BRANCH" != "$MASTER_BRANCH" ]; then
# Good
echo "${HOME}/.local/bin:${PATH}"
if [ "${CURRENT_BRANCH}" != "${MASTER_BRANCH}" ]; then
SL0002: Use == instead of = for string comparison — inside [[ ]] only
In [[ ]] test expressions, use == for string equality, not =.
Inside single-bracket [ ], keep =. == is not POSIX there: shellcheck reports SC3014 and dash fails at runtime with test: ==: unexpected operator. Never rewrite = to == in a #!/bin/sh script.
# Bad
if [[ "${CONFIGURATION}" = "Debug" ]]; then
# Good
if [[ "${CONFIGURATION}" == "Debug" ]]; then
# Also correct - leave as is
if [ "${CONFIGURATION}" = "Debug" ]; then
General Shell Script Fixes
- Quote all variable expansions —
"${VAR}"not$VAR - Use
[[over[when possible — safer, supports&&,||, pattern matching - Use
$(command)over backticks —`command`is deprecated