- pre-commit-tools
- Using pre-commit-tools with pre-commit
- Hooks available
- format-dockerfiles
- python-print-detection
- python-pprint-detection
- console-debug-detection
- console-log-detection
- console-table-detection
- react-console-error-detection
- pylint-html-report
- yaml-sorter
- debugger-detection
- json-sorter
- makefile-sorter
- requirements-sort
- ignore-file-sorter
- env-file-check
- env-example-sync
- python-logger-detection
- python-unreachable-code
- python-dead-code
- no-bare-except
- no-print-in-migration
- django-hardcoded-secret
- dockerfile-no-latest
- ts-unreachable-code
- css-duplicate-property
- css-unused-variable
- css-outside-stylesheet-detection
- no-console-warn
- react-direct-dom
- import-no-relative-parent
- no-debug-in-settings
- django-no-raw-sql
- no-sync-in-async
- fastapi-missing-response-model
- js-syntax-check
- ts-hardcoded-secret-detection
- helm-lint
- makefile-check
- claude-skill-frontmatter
- claude-agent-frontmatter
- claude-mcp-config
- generate-changelog
- python-untyped-raise
- python-mutable-default
- python-dispatch-ladder
Some out-of-the-box hooks for pre-commit.
Add this to your .pre-commit-config.yaml
- repo: https://github.com/chrysa/pre-commit-tools
- rev: v0.1.1-37
hooks:
- id: console-debug-detection
- id: console-log-detection
- id: console-table-detection
- id: react-console-error-detection
- id: format-dockerfiles
- id: dockerfile-no-latest
- id: python-print-detection
- id: python-pprint-detection
- id: no-bare-except
- id: no-print-in-migration
- id: django-hardcoded-secret
- id: yaml-sorter
- id: debugger-detection
- id: json-sorter
- id: makefile-sorter
- id: requirements-sort
- id: ignore-file-sorter
- id: env-file-check
- id: env-example-sync
- id: python-logger-detection
- id: python-unreachable-code
# optional — run manually: pre-commit run python-dead-code --hook-stage manual --all-files
- id: python-dead-code
stages: [manual]
- id: ts-unreachable-code
- id: css-duplicate-property
- id: css-unused-variable
- id: css-outside-stylesheet-detection
- id: no-console-warn
- id: react-direct-dom
- id: import-no-relative-parent
- id: no-debug-in-settings
- id: django-no-raw-sql
- id: no-sync-in-async
- id: fastapi-missing-response-model
- id: js-syntax-check
- id: ts-hardcoded-secret-detection
# requires helm in PATH
- id: helm-lint
# generate-changelog uses git-cliff; run manually or on CI
- id: generate-changelog
stages: [manual]
- id: python-untyped-raise
- id: python-mutable-default
- id: python-dispatch-ladder
# Optional — guideline-checker (structural coding guidelines)
# Validates project structure, naming conventions, and coding standards.
# See https://github.com/chrysa/guideline-checker
#- repo: https://github.com/chrysa/guideline-checker
# rev: '' # Use the ref you want to point at
# hooks:
# - id: guideline-check
# stages: [pre-push, manual]- Add shebang
# syntax=docker/dockerfile:1.4if missing - Accept any pinned
# syntax=docker/dockerfile:<version>header (e.g.1,1.7,1.7.0) and keep it as-is — only a missing header is rewritten/blocked - Warn (non-blocking) when the pinned version trails the latest stable
docker/dockerfilerelease on Docker Hub by 3 or more releases. The tag list is fetched best-effort (3 s timeout, cached 24 h) and any network failure is ignored, so the hook never breaks an offline commit - Group consecutive same-instruction blocks without blank lines
- Merge consecutive
RUNorENVinstructions on one command line with continuation
New options:
| Option | Description |
|---|---|
--sort-args |
Sort ARG instructions alphabetically |
--sort-envs |
Sort ENV instructions alphabetically |
--separate-arg-blocks |
Separate literal ARG from variable-dependent ARG (e.g. ARG FOO=${BAR}) |
-c / --config |
Path to a .format-dockerfiles.toml config file |
Config file example (.format-dockerfiles.toml):
[format-dockerfiles]
sort_args = true
sort_envs = true
separate_arg_blocks = trueDetect print() calls in Python files. Use # print-detection: disable to ignore a specific line.
Detect pprint() calls in Python files. Use # pprint-detection: disable to ignore a specific line.
Detect console.debug() calls in JavaScript/Google AppScript (.js, .gs) files.
Use // console-debug-detection: disable to ignore a specific line.
Detect console.log() calls in JavaScript/Google AppScript files.
Use // console-log-detection: disable to ignore a specific line.
Detect console.table() calls in JavaScript/Google AppScript files.
Use // console-table-detection: disable to ignore a specific line.
Run Pylint over Python files and generate an HTML report via pylint-report.
- id: pylint-with-html-report
additional_dependencies: [pylint, pylint-report]
args:
- '--output-html=reports/pylint' # directory for the HTML report (default: ./html)
- '--output-json=pylint.json' # keep the intermediate JSON (omit to auto-delete)| Option | Default | Description |
|---|---|---|
--output-html |
./html |
Directory where the HTML report is written |
--output-json |
auto-deleted | Path for the intermediate JSON report; deleted after conversion unless specified |
Sort YAML file keys alphabetically (recursive). Modifies files in-place and returns 1 if any file was changed.
To skip a file, exclude it in your .pre-commit-config.yaml.
Boolean and mixed-type keys are handled safely.
Detect debugger statements (breakpoint(), pdb.set_trace(), ipdb.set_trace(), pudb.set_trace()) in Python files.
Use # debugger-detection: disable to ignore a specific line.
Sort JSON file keys alphabetically (recursive). Modifies files in-place and returns 1 if any file was changed.
Sort Makefile rules alphabetically by target name. Modifies files in-place and returns 1 if any file was changed.
Each rule block (its leading adjacent comments, target line, prerequisite continuations and recipe) moves as a unit. Variable assignments, include directives and special targets (.PHONY, .DEFAULT, ...) keep their position — only rule blocks are reordered within the slots they occupy.
Supported files: Makefile, makefile, GNUmakefile, *.mk.
Note: sorting can change the file's first rule, which
maketreats as the default target. If you rely on the default target, declare it explicitly with.DEFAULT_GOAL := <target>.
Sort Python dependency files alphabetically. Modifies files in-place and returns 1 if any file was changed.
Supported files
| File pattern | What is sorted |
|---|---|
requirements*.txt |
All package lines (case-insensitive); comments and blank lines are moved to the top |
setup.cfg |
install_requires entries; extras keys in [options.extras_require] and their dependencies |
Example — setup.cfg
# Before
[options.extras_require]
yaml =
PyYAML==6.0.1
dead_code =
vulture>=2.0
aardvark>=1.0
# After
[options.extras_require]
dead_code =
aardvark>=1.0
vulture>=2.0
yaml =
PyYAML==6.0.1Sort the entries of ignore files (.gitignore, .dockerignore, .npmignore,
.eslintignore, .prettierignore, .helmignore, …) alphabetically
(case-insensitive). Modifies files in-place and returns 1 if any file was
changed. It targets any dotfile whose name ends in ignore (regex
(^|/)\.[^/]*ignore$).
Comment lines and blank lines are anchors: they keep their position, so
section headers and grouping are preserved. Only the pattern lines within a
contiguous run are reordered. A leading ! is ignored for the sort key so a
negation pattern stays next to the pattern it whitelists.
# Before # After
# logs # logs
*.tmp *.log
*.log *.tmp
node_modules build
build node_modulesCustom ordering — pass --sort-script PATH to delegate ordering to your own
script instead of the alphabetical default. The script receives the whole file
content on stdin and the filename as its first argument, and must print the
sorted content to stdout:
- id: ignore-file-sorter
args: ["--sort-script", "./scripts/my_ignore_sort.py"]Detect potential secrets committed in .env files (passwords, tokens, API keys, etc.).
Placeholder values (<value>, ${VAR}, changeme, etc.) are ignored.
Detect direct use of the root logging module (e.g. logging.info(...)) instead of a named logger.
Use # logger-detection: disable to ignore a specific line.
Detect explicit unreachable code — statements after return, raise, break, or continue — using Python's ast module. Works with Python 3.12, 3.13 and 3.14.
Language: Python only (AST-based, no external dependency).
No configuration needed. Returns 1 if any unreachable statement is found.
def f():
return 1
x = 2 # ← detected: unreachable code after returnUse # unreachable-code: disable on the unreachable line to suppress a specific violation.
Detect implicit dead/unused code (unused functions, variables, imports, classes) using vulture.
Language: Python only. Not language-agnostic — vulture analyses Python ASTs.
Dynamic-import awareness (default-on): names reached only through importlib.import_module, __import__, getattr/setattr/hasattr, globals()/vars()/locals() subscripts or entry_points are collected from the AST and filtered out of vulture's report, removing those false positives automatically. Disable with --no-dynamic-imports. A whitelist file still covers names this heuristic cannot reach (e.g. __all__, decorators).
Test-only detection (--detect-test-only): flags production symbols that are referenced only from test files (test helpers/backdoors leaking into prod). These findings fail the hook by default; --warn-only downgrades them to a report (genuinely dead code still fails). Detection scans both production and test files in one invocation, so set pass_filenames: false (or run over the whole tree at the manual stage) when enabling it. Test files are recognised by --test-pattern (default tests/ test_*.py *_test.py conftest.py).
This hook runs in the manual stage by default to avoid false positives on entry points.
- id: python-dead-code
stages: [manual]
args:
- '--min-confidence=80'
# Exclude paths matching these glob patterns
- '--exclude=tests/ migrations/ **/conftest.py'
# Whitelist file listing names used dynamically (suppresses false positives)
- '--whitelist=whitelist.py'
# Opt-in: also flag prod symbols used only by tests
# - '--detect-test-only'
additional_dependencies: [vulture>=2.0]| Option | Default | Description |
|---|---|---|
--min-confidence |
80 |
Minimum confidence % to report (0–100) |
--exclude |
— | Space-separated glob patterns of paths to skip |
--whitelist |
— | Vulture whitelist .py files to suppress false positives |
--no-dynamic-imports |
off | Disable dynamic-import awareness (report names reached via importlib/getattr/…) |
--detect-test-only |
off | Also flag production symbols referenced only from test files |
--test-pattern |
tests/ test_*.py *_test.py conftest.py |
File patterns marking test files |
--warn-only |
off | Report test-only findings without failing the hook |
Run manually:
pre-commit run python-dead-code --hook-stage manual --all-filesDetect explicit unreachable code — statements after return, throw, break, or continue — in TypeScript (.ts), TSX (.tsx), JavaScript (.js) and JSX (.jsx) files, using a real AST via tree-sitter.
Language: TypeScript / TSX / JavaScript / JSX (React). The TSX parser is used for .tsx/.jsx to handle JSX syntax.
Dynamic code generation: tree-sitter performs syntactic analysis only, so dynamically evaluated code is unaffected.
Use // unreachable-code: disable on the unreachable line to suppress a specific violation.
function f() {
throw new Error('not implemented');
return 42; // ← detected: unreachable code after throw
}- id: ts-unreachable-code
additional_dependencies:
- tree-sitter>=0.23
- tree-sitter-typescript>=0.23Detect duplicate CSS property declarations within the same rule block, and duplicate #id selectors across the stylesheet. Duplicate properties hide the first declaration; duplicate IDs violate CSS specificity best-practices and make maintenance harder.
Language: CSS (.css). SCSS/Less are not currently supported.
Use /* css-duplicate-property: disable */ on the duplicate property line to suppress a specific violation.
Use /* css-duplicate-id: disable */ on the duplicate ID selector line to suppress a specific violation.
.foo {
color: red; /* ← first declaration */
color: blue; /* ← detected: duplicate property "color" (first at line 2) */
}
#hero { color: red; } /* ← first occurrence */
#hero { color: blue; } /* ← detected: duplicate ID selector "hero" (first at line 6) */Nested rule blocks are tracked independently (each {…} scope has its own seen-properties map).
Detect CSS custom properties (--var-name) that are declared but never used anywhere in the file via var(--var-name).
Language: CSS (.css). SCSS/Less are not currently supported.
Use /* css-unused-variable: disable */ on the declaration line to suppress a specific violation.
:root {
--color-primary: #007bff; /* ← used below */
--color-ghost: #aaa; /* ← detected: declared but never used */
}
.btn { color: var(--color-primary); }Detect CSS written outside of .css/.scss files (CSS-in-JS). Flags the common carriers that leak styling into JS/TS/JSX/TSX/HTML/Vue sources instead of a dedicated stylesheet:
- styled-components / emotion tagged templates:
styled.button`,styled(Card)`,css`,createGlobalStyle`,keyframes`,injectGlobal` - inline
<style>blocks embedded in markup
Language: .js, .jsx, .ts, .tsx, .html, .htm, .vue.
Inline style={{ ... }} attributes are handled separately by react-no-inline-styles.
Use // css-outside-stylesheet: disable on the offending line to suppress a specific violation.
const Button = styled.button` // ← detected: CSS in a .tsx file
color: red;
`;Detect console.error() calls in JavaScript/TypeScript/React files. Complements console-log-detection, console-debug-detection and console-table-detection.
Language: .js, .ts, .jsx, .tsx.
Use // console-error-detection: disable on the line to suppress a specific violation.
Detect bare except: clauses (without specifying an exception type) in Python files. Bare excepts catch all exceptions including SystemExit, KeyboardInterrupt and GeneratorExit, which can mask serious errors.
Use # no-bare-except: disable to suppress a specific line.
# WRONG
try:
do_something()
except: # ← detected
pass
# OK
try:
do_something()
except ValueError:
passDetect print() calls in Django migration files (migrations/*.py). Print statements committed in migrations will be shown every time migrations run.
Use # no-print-in-migration: disable to suppress a specific line.
Detect hardcoded secrets directly assigned in Python files. Catches patterns like SECRET_KEY = "...", PASSWORD = "...", API_KEY = "...", TOKEN = "..." and PRIVATE_KEY = "...".
Values referencing environment variables (os.environ, os.getenv, env(), config(), etc.) are not flagged.
Use # django-hardcoded-secret: disable to suppress a specific line.
# WRONG — detected
SECRET_KEY = 'django-insecure-abc123'
# OK — not flagged
SECRET_KEY = os.environ['SECRET_KEY']
SECRET_KEY = env('SECRET_KEY')Detect FROM image:latest instructions in Dockerfiles. Using :latest makes builds non-reproducible.
FROM scratch is always allowed.
Use # dockerfile-no-latest: disable to suppress a specific line.
# WRONG
FROM python:latest
# OK
FROM python:3.12-slimCheck that .env and .env.example files contain the same set of keys. Ensures that new environment variables added to .env are documented in .env.example (with empty or placeholder values), and vice-versa.
- id: env-example-sync
args:
- '--env-file=.env' # default
- '--example-file=.env.example' # default| Option | Default | Description |
|---|---|---|
--env-file |
.env |
Path to the private .env file |
--example-file |
.env.example |
Path to the public example file |
Detect console.warn() calls in JavaScript/TypeScript/React files (.js, .gs, .ts, .jsx, .tsx).
Use // no-console-warn: disable to suppress a specific line.
Detect direct DOM manipulation (document.getElementById, document.querySelector, etc.) in React files (.jsx, .tsx).
Direct DOM access bypasses React's virtual DOM — use refs or state instead.
Use // react-direct-dom: disable to suppress a specific line.
Detect deep relative parent imports (../../) in TypeScript/JavaScript files.
Use path aliases (@/) instead. Two or more ../ levels trigger a violation.
Use // import-no-relative-parent: disable to suppress a specific line.
Detect DEBUG = True in Django settings files (settings*.py).
Use # no-debug-in-settings: disable to suppress a specific line.
Detect raw SQL queries (.raw() and cursor.execute()) in Django Python files.
These patterns bypass the ORM and risk SQL injection.
Use # django-no-raw-sql: disable to suppress a specific line.
Detect synchronous blocking calls inside async def functions:
| Blocked call | Suggested async replacement |
|---|---|
time.sleep() |
asyncio.sleep() |
requests.get/post/put/delete/patch() |
httpx.AsyncClient or aiohttp |
subprocess.run/call/check_output() |
asyncio.create_subprocess_exec |
Use # no-sync-in-async: disable to suppress a specific line.
Detect FastAPI route handlers (@app.get, @app.post, etc.) that do not declare a response_model= parameter.
This prevents automatic response schema generation and validation.
Use # fastapi-missing-response-model: disable on the decorator line to suppress.
- id: fastapi-missing-response-model
files: '(routers?|views?|api)/.*\.py$'Validate JavaScript and Google Apps Script (.js, .gs) file syntax using node --check.
Requires Node.js to be installed in PATH. If Node.js is not available, the hook exits 0 (safe skip).
- id: js-syntax-check
files: '\.(js|gs)$'Detect hardcoded API keys, tokens and passwords in TypeScript/JavaScript files (.js, .ts, .jsx, .tsx).
Catches patterns like const API_KEY = "...", const token = "sk_live_...", AWS key prefixes (AKIA...), GitHub tokens (ghp_...), and Stripe keys (sk_live_...).
Values referencing environment variables (process.env, import.meta.env) are not flagged. Short values (< 8 chars) are ignored.
Use // ts-hardcoded-secret: disable on the line to suppress a specific violation.
// WRONG — detected
const API_KEY = 'my-secret-api-key-value-hardcoded';
// OK — not flagged
const API_KEY = process.env.API_KEY;Run helm lint --strict on all charts found in charts/<namespace>/<service>/ (depth 2 under charts/).
Requires helm to be installed in PATH.
Expected directory structure:
charts/
<namespace>/
<service>/ ← helm lint --strict is run here
Chart.yaml
values.yaml
- id: helm-lint
files: ^charts/
pass_filenames: falseEnforce the chrysa archetype-tiered Makefile contract (shared-standards
EXECUTION_STANDARD.md §1). Each Makefile must declare its tier on a marker line:
# makefile-tier: lib # one of: lib | python-app | fullstack | infraThe hook then fails on: a missing/invalid tier marker, a required target absent for the
tier, a forbidden target name (fmt/type-check/tests), a legacy docker-compose <cmd>
invocation, a glued docker compose typo, a missing help target or .PHONY line, or a
lint/test/format rule that references a directory which does not exist. Targets missing from
.PHONY and the absence of ## self-documenting comments are reported as warnings.
- id: makefile-check
files: (^|/)Makefile$Validate the YAML front matter of Claude Code skills. A SKILL.md without front matter is
silently invisible to the model — the file looks fine to a human reviewer but the skill is
never offered. Legacy skills written as a plain # Skill: <title> heading get an explicit
migration hint.
Fails on: a missing, unclosed, or invalid front matter block; a missing name/description;
a name that is not lower-case kebab-case or does not match the containing directory; a
description too short to act as a trigger. Unknown keys and an over-long description are
warnings.
- id: claude-skill-frontmatter
files: (^|/)SKILL\.md$| Option | Default | Description |
|---|---|---|
--min-description |
20 |
Minimum description length, in characters |
--max-description |
1024 |
Length above which description is warned about |
--allow-key |
— | Extra front matter key tolerated without warning (repeatable) — use it for a repo-local metadata convention such as origin/category |
Validate the YAML front matter of Claude Code subagents (agents/*.md). An agent whose front
matter is missing or malformed is never registered, so the Agent tool silently cannot dispatch
to it.
Fails on: a missing, unclosed, or invalid front matter block; a missing name/description;
a name that is not kebab-case or does not match the file stem; a tools value that is
neither a list nor a comma-separated string; an empty prompt body. Unknown keys and an
unrecognised model alias are warnings.
- id: claude-agent-frontmatter
files: (^|/)agents/[^/]+\.md$| Option | Default | Description |
|---|---|---|
--allow-key |
— | Extra front matter key tolerated without warning (repeatable) |
Validate Claude Code MCP server configuration (.mcp.json). An entry is either stdio
(command + optional args) or remote (url with an http/sse transport); mixing both or
declaring a contradictory transport yields a server that fails to connect at session start
with no actionable message.
Fails on: invalid JSON; a missing or malformed mcpServers mapping; a server declaring
neither or both of command/url; a transport contradicting the declared keys; malformed
args/env/headers; any credential-looking value inlined instead of referenced as
${VAR}.
- id: claude-mcp-config
files: (^|/)\.?mcp(_servers)?\.json$Generate or update a CHANGELOG.md using git-cliff on every commit to main.
Requires git-cliff to be installed in PATH.
This hook runs in the manual stage by default — trigger it via CI or explicitly:
pre-commit run generate-changelog --hook-stage manual- id: generate-changelog
stages: [manual]Capture screenshots of the UI screens affected by a commit and publish them to the README and/or a Notion page. Two composable hooks linked by a manifest.
Note on Notion: the Notion publisher appends image blocks on each run, so
committing repeatedly with notion.enabled will accumulate duplicate images on
the page. The README target is idempotent; the Notion target is not (idempotent
Notion publishing is a planned follow-up). Enable Notion when you want an
append-only log of screenshots, or publish to Notion deliberately rather than on
every commit.
- repo: https://github.com/chrysa/pre-commit-tools
rev: v0.0.34
hooks:
- id: screenshot-capture
- id: screenshot-publishAdd a .screenshot-sync.yaml to the consuming repo (absent → both hooks are
no-ops):
strategy: glob-url # glob-url | storybook | fixed-routes
base_url: http://localhost:5173
output_dir: docs/screenshots
viewport: { width: 1280, height: 800 }
strict: false # true = blocking on failure; false = warn + skip
routes:
- { match: "src/pages/Login.*", url: /login, name: login }
publish:
readme: { enabled: true, file: README.md, marker: screenshots }
notion: { enabled: false, page_id: "", image_base_url: "" } # NOTION_API_KEY via envscreenshot-capture renders the routes whose source files changed (Playwright),
writes PNGs + a manifest under output_dir, and stages them. screenshot-publish
reads the manifest and updates the README section between
<!-- screenshots:start --> / <!-- screenshots:end --> and/or appends image
blocks to the Notion page. By default neither hook blocks the commit; set
strict: true to make environment/network failures blocking. Playwright browser
binaries are not auto-installed — run playwright install chromium once per repo.
| Hook id | Purpose | Disable escape |
|---|---|---|
python-no-external-tool-config |
Detect forbidden standalone tool config files (ruff.toml, mypy.ini, pytest.ini, .coveragerc) — configuration must live in pyproject.toml |
— |
python-no-setup-files |
Detect setup.py / setup.cfg packaging files — use pyproject.toml instead |
— |
python-cors-allow-all |
Detect wildcard CORS (allow_origins=['*'] / CORS_ALLOW_ALL_ORIGINS=True) |
# cors-allow-all: disable |
python-no-create-all |
Detect Base.metadata.create_all() / engine.create_all() calls outside migrations/alembic |
# no-create-all: disable |
python-os-environ-direct |
Detect direct os.environ[...] / os.getenv(...) access outside settings/config modules |
# os-environ-direct: disable |
python-file-too-long |
Detect Python files exceeding 500 lines (split the module) | — |
python-function-too-long |
Detect Python functions exceeding 50 lines (extract helpers) | — |
docker-compose-missing-restart |
Detect Docker Compose services missing restart: unless-stopped |
no inline escape; suppress via repo exclude: or SKIP=docker-compose-missing-restart |
docker-run-host-user |
Detect docker run invocations that bind-mount the repository without --user — the container runs as root and writes root-owned files into the tree |
# docker-run-host-user: disable |
compose-dev-hot-reload |
Detect Compose dev services with neither a bind mount nor a develop.watch sync — a source edit would need a rebuild, so the service cannot hot-reload |
# compose-dev-hot-reload: disable |
react-useeffect-fetch |
Detect fetch/axios calls inside a React useEffect callback (use a query library instead) |
// react-useeffect-fetch: disable |
| Hook id | Purpose | Disable escape |
|---|---|---|
sentry-no-default-pii |
Detect send_default_pii=True in Sentry initialisation — transmits PII to Sentry by default |
# sentry-no-default-pii: disable |
react-no-token-in-localstorage |
Detect auth tokens stored in localStorage (prefer httpOnly cookies) |
// react-token-localstorage: disable |
python-pii-in-logs |
Detect PII (email, token, password, card number, SSN, IBAN, phone) passed to logging calls | # pii-in-logs: disable |
django-cookie-security |
Enforce SESSION_COOKIE_SECURE, SESSION_COOKIE_HTTPONLY, CSRF_COOKIE_SECURE and CSRF_COOKIE_HTTPONLY in Django production settings |
— |
fastapi-cookie-insecure |
Detect response.set_cookie() calls missing secure=True, httponly=True, or samesite= |
# fastapi-cookie-insecure: disable |
pii-hardcoded |
Detect hardcoded personal data (French NIR, IBAN, real email address, FR phone number) in source files | # pii-hardcoded: disable / // pii-hardcoded: disable |
Detect raise of a generic builtin exception (Exception, BaseException,
RuntimeError) instead of a domain-specific exception class. A bare builtin gives
callers nothing to catch narrowly and carries no stable machine-readable code
(chrysa standard: Raised errors are typed).
A bare raise (re-raising the active exception) is never flagged.
Use # python-untyped-raise: disable to suppress a specific line.
- id: python-untyped-raiseDetect mutable default arguments — [], {}, set(), list(), dict(),
deque(), defaultdict(), Counter() — in function and method signatures,
positional and keyword-only alike. The default is evaluated once at definition
time and shared by every call. Default to None and build the value in the body.
Use # python-mutable-default: disable to suppress a specific line.
- id: python-mutable-defaultDetect if/elif ladders that dispatch one value against constant literals
(kind == 'a' / elif kind == 'b' / …, or in against constant tuples) — the shape
the chrysa standard Prefer a lookup table to a state machine requires to be a
dict from key to handler or value, so a new case is a new row instead of an edit
to control flow.
Chains of unrelated conditions are not flagged; only ladders whose every branch tests the same subject. The default threshold is 3 branches, tunable:
- id: python-dispatch-ladder
args: [--max-branches=5]Use # python-dispatch-ladder: disable on the opening if to suppress a ladder.