mv .mypycache .mypycache.corrupt-$(date +%s) # keep for forensics
Diagnosis rule: when a strict mypy gate suddenly INTERNAL ERRORs on an unchanged tree, the .mypy_cache is the suspect — not the code. mypy 2.3.0's cache is a sharded SQLite store (.mypy_cache/<pyver>/cache.*.db) written with PRAGMA synchronous=OFF; a torn/partial write (Ctrl+C mid-run, OOM killer, interrupted CI container, disk pressure) leaves a corrupt db that mypy opens with sqlite3.connect + executescript(SCHEMA) and crashes before analyzing anything. The file named in the error is just whichever module was loading when the corrupt entry was read — for the incident that was a uvicorn site-packages file.
The fix — move the cache aside, don't delete it, and re-run:
# unchanged tree, gate just started INTERNAL ERROR-ing:
mv .mypy_cache .mypy_cache.corrupt-$(date +%s) # keep for forensics
mypy --strict . # fresh-cache rebuild -> exit 0
CI-safe wrapper (drops into any gate; MYPY and MYPY_CACHE_DIR overridable):
#!/usr/bin/env bash
# fix_mypy_cache.sh
set -u
MYPY="${MYPY:-python3 -m mypy}"
CACHE_DIR="${MYPY_CACHE_DIR:-.mypy_cache}"
read -r -a MYPY_CMD <<< "$MYPY"
"${MYPY_CMD[@]}" "$@" >/tmp/mypy_gate.out 2>&1; rc=$?
[ $rc -eq 0 ] && { cat /tmp/mypy_gate.out; exit 0; }
if ! grep -q "INTERNAL ERROR" /tmp/mypy_gate.out; then
cat /tmp/mypy_gate.out; exit $rc # real type errors -> fix code
fi
echo "INTERNAL ERROR -> corrupt cache at $CACHE_DIR; moving aside"
mv "$CACHE_DIR" "${CACHE_DIR}.corrupt-$(date +%Y%m%d-%H%M%S)"
"${MYPY_CMD[@]}" "$@" >/tmp/mypy_gate.out 2>&1; rc=$?
cat /tmp/mypy_gate.out
[ $rc -eq 0 ] && echo "fresh-cache rebuild passed — cache corruption confirmed, no code change needed"
exit $rc
Hardening (prevent recurrence):
- Add .mypy_cache/ to .gitignore (already auto-created) and treat it as ephemeral — never cache it in CI artifacts.
- --cache-dir=/dev/null (or cache_dir = "/dev/null") disables persistence; verified safe, at the cost of no incremental speedup.
- --no-incremental does not help — mypy still opens the metastore (verified against 2.3.0 source: create_metastore builds SqliteMetadataStore regardless of incremental mode; only os.devnull short-circuits).
Reproduced the exact incident with mypy 2.3.0 (Python 3.14.4, uvicorn installed) on a repo whose app imports `uvicorn`/`Config`/`Server`: | # | Scenario | Result | |---|----------|--------| | 1 | Tick N: strict run builds cache | `Success` exit 0 (gate passed) | | 2 | Truncated a `cache.*.db` to 512B, tree **unchanged**, re-run | `error: INTERNAL ERROR`, exit 2 | | 3 | `--show-traceback` | `sqlite3.DatabaseError: malformed database schema (?)` in `mypy/metastore.py` `connect_db` — crash at cache open, before analysis | | 4 | **Fix:** `mv .mypy_cache` aside, re-run | fresh rebuild `Success` exit 0 | | 5 | Next tick, same tree | `Success` exit 0 (idempotent, gate green) | | 6 | Zeroed 4KB mid-file corruption | INTERNAL ERROR, exit 2 | | 7 | Random-garbage corruption | INTERNAL ERROR, exit 2 | | 8 | Payload-only corruption (data pages, schema intact) | INTERNAL ERROR but traceback now in `find_cache_meta → _load_ff_file → metastore._query` — the "crash attributed to a module file" variant matching the uvicorn report | | 9 | Delete only the one corrupt `cache.*.db` | self-heals (missing file = rebuild), exit 0 — confirms corruption is per-shard | | 10 | Restore pristine cache | exit 0 — proves code was never at fault | | 11 | `--no-incremental` on corrupt cache | still exit 2 — not a fix | | 12 | `--cache-dir=/dev/null` on corrupt cache | exit 0 — valid alternative | | 13–19 | `fix_mypy_cache.sh`: green passthrough · auto detect+move+rebuild (exit 0) · forensic `.mypy_cache.corrupt-*` preserved · real type error NOT misattributed (exit 1, proper diagnostics) · clean-tree first run · larger uvicorn tree · repeat corruption cycle | all as designed | Root-cause chain confirmed in mypy 2.3.0 source: `mypy/metastore.py` comment admits "we may get corrupt cache after e.g. Ctrl + C" due to `PRAGMA synchronous=OFF`, and `SqliteMetadataStore.__init__` eagerly `connect_db()`s every shard — so any damaged db is fatal at startup or at first module-metadata read.
{"model": "deepseek-v4-flash", "problem_class": "python-mypy-cache-corruption", "result": "passed", "tests": 19}