Tinker AI
Read reviews
intermediate 6 min read

Aider with pre-commit hooks: making the linter the model's feedback loop

Published 2026-04-16 by Owner

Aider commits after every successful edit by default. If you have pre-commit hooks installed, those hooks run on each commit. This is either a powerful feedback loop or an infinite loop, depending on configuration. Here’s the version that works.

The integration aider does for free

When aider runs git commit after an edit, your pre-commit hooks run normally. If they fail, the commit fails. Aider sees the failure output.

For lint hooks that auto-fix (Black, Prettier, eslint with --fix), pre-commit modifies your files. Aider notices the modifications and incorporates them into the next turn. The model sees what the linter changed and learns to produce code that matches the linter’s expectations on subsequent edits.

This is the loop you want: model produces code, linter corrects it, model learns the pattern.

The loop you don’t want

For lint hooks that don’t auto-fix (mypy, eslint without —fix, custom scripts), pre-commit fails the commit. Aider sees the error output and tries again. If the model can’t figure out the fix, it tries again. And again. Each attempt costs tokens.

I’ve seen aider sessions burn $5 in 20 minutes on a stubborn mypy error the model couldn’t solve. The fix: limit the retry count.

In .aider.conf.yml:

auto-test: true
test-cmd: pre-commit run --files {files}
auto-commits: true
max-attempts: 3

max-attempts: 3 caps the retry loop. After three failures, aider stops trying and asks you what to do. This is the difference between “expensive feedback loop” and “infinite token sink.”

Hook ordering matters

Pre-commit runs hooks in the order configured. The order affects how aider learns.

Bad order:

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: mypy
        # ...
  - repo: https://github.com/psf/black
    rev: 24.0.0
    hooks:
      - id: black

Why this is bad: mypy runs first. It fails on a formatting issue (e.g., line too long causing a multi-line type annotation that mypy can’t parse). Aider sees a confusing mypy error. It tries to fix the type. Black runs next, reformats the code, and now the mypy error is in a different place.

Better order:

repos:
  - repo: https://github.com/psf/black
    rev: 24.0.0
    hooks:
      - id: black
  - repo: local
    hooks:
      - id: mypy
        # ...

Formatters first, then validators. Aider sees the formatted code and the validation errors against that code. The errors are stable.

Hooks that work badly with aider

A few categories of hooks I’ve removed from aider-active branches:

Slow hooks (>5 seconds). Pre-commit runs on every aider commit. A 10-second hook turns a 30-second aider iteration into a 40-second one. Compounded over a session, this is meaningful. Move slow hooks (full test runs, full type-check across the project, security scans) to CI instead.

Hooks that require network access. Hooks that pull from the internet (license checks, dependency vulnerability scans) introduce flakiness. Aider sees a network failure as a hook failure and retries. Move these to CI.

Hooks that have non-deterministic output. I had a hook that was checking for “unused” exports and producing different results based on which order files were checked. Aider couldn’t make progress because the same code was passing once and failing the next.

Hooks that auto-modify in non-stable ways. A hook that adds copyright headers based on the current year is fine. A hook that randomly reorders imports based on some metric the linter computes inconsistently is not — aider sees the reorder, applies its own change, and the next pre-commit run reorders again.

Hooks that work great with aider

Black, Prettier, ruff format. Auto-fix formatters. Aider learns your formatting standard within 2-3 iterations and produces formatted code thereafter.

Eslint with —fix. Same pattern. After a few sessions, aider produces eslint-clean code on first attempt for the rules it has fixers for.

Type-checkers in fast mode. mypy —no-incremental is slow; mypy with cache is fast. Tsc —noEmit on a single file is fast; tsc —build is slow. Configure for speed.

Custom validators that produce clear error messages. A hook that fails with “missing copyright header on file X” is something aider can fix. A hook that fails with “Error: validation failed” is not.

A real configuration

Here’s the pre-commit config I use on a Python/TypeScript project:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.5.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        types_or: [javascript, typescript, tsx]

  - repo: local
    hooks:
      - id: typescript-check
        name: tsc
        entry: bash -c 'cd web && tsc --noEmit'
        language: system
        files: ^web/.*\.(ts|tsx)$
        pass_filenames: false

The pattern: auto-fixers first (trailing whitespace, ruff, prettier), then validators (tsc). The validators run only on relevant files (files: pattern), keeping them fast. mypy is not in this set — it runs in CI, not pre-commit.

What aider does after the linter changes things

When pre-commit auto-fixes files, those changes are part of the commit aider just made. The next time you ask aider to do something, it sees the linter-corrected version. The model picks up patterns from this. After about a week of consistent use:

  • The model produces code that matches your formatting on first attempt
  • It uses your project’s preferred patterns (return types, error handling, etc.) more consistently
  • Pre-commit failures drop noticeably

This is the actual reason to integrate aider with pre-commit, not the immediate convenience. The integration trains the model on your codebase’s conventions through repeated exposure to the corrections.

When to disable auto-commits

Aider’s auto-commits can be turned off:

auto-commits: false

I disable this when I’m doing exploratory work and don’t want to commit yet. The downside is losing the pre-commit feedback loop until I commit manually. The tradeoff is worth it for short exploration sessions; not worth it for production work.

For production work, auto-commits + pre-commit + small max-attempts is the configuration that gets the most value out of aider’s design.