Orchestrata Ultimate › Gates › mypy
Gate: mypy
At a glance
| Purpose | Refuse a mypy type error in src/ locally, so the first sight of one is never a failed pipeline. |
| Where it runs | The PostToolUse hook typecheck-on-write.sh (on every write); the scripts/gates.py battery (registered as mypy); CI's lint job; per file inside the commit review (code-review.py's MyPyChecker, run by scripts/gate.py review-staged). |
| Trigger | A file write in a session; a battery run; a CI pipeline; every git commit that stages .py files under src/. |
| Exact command | Battery argv: uv run mypy --cache-dir .cache/mypy src/. CI: uv run mypy src/. Commit review: <interpreter> -m mypy --no-error-summary <file>, 30 s timeout. |
| Blocks | mypy's own exit code: non-zero on errors, 0 clean. In the commit review, mypy findings and MYPY_TIMEOUT / MYPY_NOT_FOUND are ERROR and block the commit. |
| Ruling / ADR | None names this gate directly; the commit-review layer is RUL-008. |
| Last recorded verdict | ok: true in the primary checkout's .cache/gates.json (file last written 2026-09-22 10:01 local). wt-MAP-PRECOMMIT has no .cache/gates.json. |
What it refuses
A mypy type error in src/ — caught locally, with the exact command CI runs, so the
first sight of a type error is never a failed pipeline.
Why it exists
The gates.py registry comment records the incident:
"THE SAME COMMAND CI's
lintjob runs. Until 2026-08-07 mypy was a CI-ONLY gate: this battery could report 10/10 whileuv run mypy src/was red, so the first sight of a type error was a failed pipeline — which is exactly what happened (16 errors, two files, after a local run reported 0 from a stale incremental cache)."
And the cache-location decision:
"--cache-dir is pinned INSIDE the repo on purpose. The default
.mypy_cacheresolves per working directory, and sibling worktrees sharing this checkout's caches have already produced results attributed to the wrong tree. Pinning it makes a gate PASS mean this tree."
How it runs
- Command (battery argv):
uv run mypy --cache-dir .cache/mypy src/ - When it fires: four layers, earliest first —
- On write — the PostToolUse hook
typecheck-on-write.shruns full-scope mypy the moment a file lands ("Measured on this repo:mypy --cache-dir .cache/mypy src/→ 1.166s (all 899 files, warm). One second."); - the
scripts/gates.pybattery (registered asmypy); - CI's lint job — validate-ci's Gate 7 requires "Architecture checker or mypy
present". Measured in
.gitlab-ci.ymlon 2026-10-23: the job runsuv run mypy src/, without the battery's--cache-dir .cache/mypy, so the registry comment's "THE SAME COMMAND" no longer holds word for word; - the commit review —
code-review.py'sMyPyCheckerruns<interpreter> -m mypy --no-error-summary <file>per staged file with a 30 s timeout (read through the AST toolkit).
- On write — the PostToolUse hook
- Inputs: fingerprint roots
_SRC = ("src", "scripts", "tests", "pyproject.toml"). - Caching: the verdict is cached in
.cache/gates.jsonagainst_fingerprint(roots): a sha256 over the relative path and bytes of every file under those roots whose suffix is.py,.toml,.pl,.json,.mdor.sh(_interesting(), read through the AST toolkit on 2026-10-23). There is no per-gate suffix mask: this page previously quoted aGATE_MASKScomment, andgit log --all -S GATE_MASKS -- scripts/gates.pyfinds no commit that ever contained one. A.pyistub change does not invalidate the cached verdict (derived from the code, not measured).
Typical failures and how to fix them
- Exit code: mypy's own — non-zero on errors, 0 clean.
- What a PASS looks like (real recorded battery output,
.cache/gates.json):3.4s | pyproject.toml: note: unused section(s): module = ['tests.*'] Success: no issues found in 934 source files - What a failure looks like (representative): standard mypy diagnostics —
path:line: error: <message> [<code>]— with the count. - Un-blocking: fix the type error. Per the project's type-debt discipline (see
commit
87b8bee4, "Type debt: fix what the errors were pointing at, not the errors"), the fix is the underlying contract, not an# type: ignore. MYPY_TIMEOUTblocks a commit on a cold cache. The commit review's per-file mypy call has a 30 s timeout and reportsMYPY_TIMEOUTat ERROR. Recorded as defect D3 in the 2026-10-22 brief improvement log ("The commit review's mypy step timed out at 30 s on a cold.mypy_cache"). Fix: warm the cache on the changed files first (python -m mypy --no-error-summary <changed .py files>from the checkout's venv), then commit again.- Trap: a plain
uv runre-syncs the venv editable. The battery's argv isuv run mypy ...without--no-sync. Memoryuv-run-flips-venv-editable(measured 2026-10-22) records that a plainuv runin an orchestrata checkout re-syncs the venv editable and the pre-push source-provenance gate then refuses the push..envrcline 47 exportsUV_NO_EDITABLE=1, which hooks and direnv-less worktrees do not get. Whether the battery'suv run mypyflips the venv was not measured.
Wiring
- Invokers:
typecheck-on-write.sh(PostToolUse),scripts/gates.py(battery), CI's lint job,code-review.pyviagate.py review-staged. - Tests: exercised through the battery's tests (
tests/scripts/test_gates.py,test_gates_coverage.py) and the hook's own coverage (tests/scripts/test_session_start_hooks.pycovers the non-gating hook fleet; the write-time layer is part of the registered PostToolUse pair).
Related
- Gates master index
- Development workflow
- ruff — the lint sibling
- review-staged — the commit review that runs mypy per file
- commit-precheck — the same review without committing
- ci-pipeline — CI's lint job
- source-provenance — the gate an editable venv trips
- RUL-008 — Review before every commit
- Session-hooks family —
typecheck-on-write.sh, "the EARLIEST mypy layer"