Skip to content

Replace pre-commit with prek for faster linting #143148

Closed

Description

@juhannc

Feature or enhancement

Proposal:

First, I hope this is the correct issue template. If not, please let me know, and I can resubmit the proposal using the proper template.

prek is a "reimagined version of pre-commit, [...] designed to be a faster, dependency-free and drop-in alternative".
It has already been adopted by major projects from Apache, FastAPI, and Django (see prek/Who is using prek?).

Running prek against the entire CPython codebase yields a speedup of almost 2.5x without a cache and 1.25x with a warm cache.

hyperfine --warmup 2 --runs 5 \
  --prepare "prek cache clean" "prek run --all-files" \
  --prepare "pre-commit clean" "pre-commit run --all-files"
Benchmark 1: prek run --all-files
  Time (mean ± σ):     19.835 s ±  0.290 s    [User: 50.857 s, System: 6.248 s]
  Range (min … max):   19.527 s … 20.201 s    5 runs

Benchmark 2: pre-commit run --all-files
  Time (mean ± σ):     48.944 s ±  0.448 s    [User: 76.063 s, System: 6.096 s]
  Range (min … max):   48.522 s … 49.681 s    5 runs

Summary
  prek run --all-files ran
    2.47 ± 0.04 times faster than pre-commit run --all-files
hyperfine --warmup 2 --runs 5 \
  "prek run --all-files" \
  "pre-commit run --all-files"
Benchmark 1: prek run --all-files
  Time (mean ± σ):      6.763 s ±  0.124 s    [User: 35.990 s, System: 2.134 s]
  Range (min … max):    6.589 s …  6.900 s    5 runs

Benchmark 2: pre-commit run --all-files
  Time (mean ± σ):      8.537 s ±  0.048 s    [User: 43.797 s, System: 1.903 s]
  Range (min … max):    8.484 s …  8.608 s    5 runs

Summary
  prek run --all-files ran
    1.26 ± 0.02 times faster than pre-commit run --all-files

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

No response

Linked PRs

Activity

  1. hugovk commented on Dec 29, 2025

    @hugovk
    Member

    So, just to get it straight, you propose to first look for a system prek. If that doesn't exist, look for pre-commit from the venv. And what if that also doesn't exist? Should pre-commit be installed or prek?

    (With extra config, prek also allows running hooks in parallel, which would be even quicker.)

    Exactly, although I guess that should be a different pull request.

    Yep, let's keep this one simpler.

    I suggest:

    • CI: Use j178/prek-action
    • Makefile: If others don't want to use prek in the venv, then we can have it check if a system prek exists, and use that; otherwise fallback to the current pre-commit in the venv.

    So, just to get it straight, you propose to first look for a system prek. If that doesn't exist, look for pre-commit from the venv. And what if that also doesn't exist?

    Then install pre-commit into the venv (this is the current behaviour).

    Should pre-commit be installed or prek? I would propose the following to ease the way for a future with full prek adoption, especially with new developers coming in:

    1. system prek
    2. system pre-commit
    3. venv pre-commit
    4. venv prek

    Or simpler:

    1. system prek
    2. venv pre-commit

    In practice, the only way a tool ends up in the venv is if the Makefile installs it when not found.

  2. juhannc commented on Dec 29, 2025

    @juhannc
    ContributorAuthor

    Then install pre-commit into the venv (this is the current behaviour).

    Or simpler:

    1. system prek
    2. venv pre-commit

    In practice, the only way a tool ends up in the venv is if the Makefile installs it when not found.

    But then there is no clear way to unite the tooling again in the future. I don't think a discrepancy between the tools used locally and in the CI is a clean approach.
    Also, what about the developer guide? Should it mention prek at all? Because that would put additional strain on the developer, first to decide which tool to use and then potentially manually installing something.

  3. emmatyping commented on Dec 30, 2025

    @emmatyping
    Member

    My understanding is that most of performance improvements from prek for a cold cache comes from using uv to install hooks. Perhaps instead of switching to an entirely different tool, we could introduce pre-commit-uv which sees significant speedups for the cold cache case without needing to change anything else:

    Cold cache:

    Benchmark 1: prek run --all-files
      Time (mean ± σ):      6.054 s ±  0.083 s    [User: 9.335 s, System: 3.220 s]
      Range (min … max):    5.954 s …  6.160 s    5 runs
     
    Benchmark 2: pre-commit run --all-files
      Time (mean ± σ):     20.475 s ±  0.273 s    [User: 25.388 s, System: 4.260 s]
      Range (min … max):   20.194 s … 20.922 s    5 runs
    
    Benchmark 3: pre-commit run --all-files (with pre-commit-uv)
      Time (mean ± σ):     13.364 s ±  0.254 s    [User: 20.372 s, System: 3.699 s]
      Range (min … max):   13.070 s … 13.661 s    5 runs
    

    Warm cache:

    Benchmark 1: prek run --all-files
      Time (mean ± σ):      2.646 s ±  0.007 s    [User: 3.873 s, System: 1.666 s]
      Range (min … max):    2.635 s …  2.652 s    5 runs
     
    Benchmark 2: pre-commit run --all-files
      Time (mean ± σ):      3.126 s ±  0.002 s    [User: 11.106 s, System: 1.821 s]
      Range (min … max):    3.124 s …  3.130 s    5 runs
    
    Benchmark 3: pre-commit run --all-files (with pre-commit-uv)
      Time (mean ± σ):      3.105 s ±  0.011 s    [User: 10.940 s, System: 1.833 s]
      Range (min … max):    3.092 s …  3.117 s    5 runs
    
  4. hugovk commented on Dec 30, 2025

    @hugovk
    Member

    My understanding is that most of performance improvements from prek for a cold cache comes from using uv to install hooks.

    A bigger improvement comes from prek cloning/installing repos in parallel.

    prek's 6.1s is a lot better than pre-commit-uv's 13.4s (and pre-commit's 20.5s).

    prek also reimplements some hooks in Rust, which helps with warm cache speeds.

  5. dolfinus commented on Dec 30, 2025

    @dolfinus

    IMHO the more important feature prek 0.2.23+ has is priority - hooks that modify file content (ruff format, etc) can be run sequentially, and read-only hooks (ruff check, mypy, github workflow linters) can be run in parallel. For large number of read-only hooks the speed up can be huge.
    Hook installation speed with cold cache is a nice improvement, but this is important only in CI workflows, not on local machine.

  6. picnixz commented on Dec 30, 2025

    @picnixz
    Member

    But then there is no clear way to unite the tooling again in the future. I don't think a discrepancy between the tools used locally and in the CI is a clean approach.

    And I don't want tools that are not widely used as others. I'm ok with ruff, etc because those are tools I can trust and others can trust. It's more about how widespread prek is. If it were used as much as pre-commit I'd probably have no issue with it, but for now, I definitely want to see whether the project will be as wide spread as pre-commit. To be clear, I'm willing to cast my core dev vote against the change if I'm forced to install prek. What I eventually want is one tool, not multiple ones used multiple projects that then pollute my global ~/.cache folder. FTR, I'm using uvx pre-commit because I have too many Python versions installed and my default Python version (thanks to my OS) is Python 3.6 (though I may need to update it at some point, I was waiting for OpenSUSE Leap 16.0 to come out) and I prefer having very small system-wide installations.

    And what if that also doesn't exist? Should pre-commit be installed or prek?

    In this case, I would install nothing and leave it to the developer. It's their choice to install additional software and their choice whether they want to trust it.

    IMHO the more important feature prek 0.2.23+ has is priority

    There is a huge warning about it:

    The following configuration keys are prek-specific and are not supported by the original pre-commit (at least at the time of writing). If you run the same config with pre-commit, it may warn about unexpected/unknown keys.

    In this case, using priority but ending up with warnings is definitely something I don't want either.

  7. emmatyping commented on Dec 30, 2025

    @emmatyping
    Member

    Regarding priority, I have doubts as to the correctness of running hooks in parallel, that seems likely to have unexpected failure modes.

    I also agree with all of the points @picnixz made, especially that I don't think prek is widely used enough to adopt in CPython yet.

  8. added a commit that references this issue on Jan 2, 2026
  9. added 2 commits that reference this issue on Jan 2, 2026
  10. added 2 commits that reference this issue on Jan 2, 2026
  11. hugovk commented on Jan 8, 2026

    @hugovk
    Member

    OK, CI is updated to use prek, we can revisit later about using it in the Makefile when adoption is higher. Right now, pre-commit has 81m/month downloads, and prek is 14m/month.

    If you're not using the Makefile, you can already use prek instead of pre-commit if you prefer.

    Regarding priority, I have doubts as to the correctness of running hooks in parallel, that seems likely to have unexpected failure modes.

    I think the trick is to keep the existing order for hooks that can write (that is, don't define a priority, and they'll be run in sequential order), but the read-only hooks can then run in parallel afterwards (so give them all something like priority: 100).

    I'm running with this in a .prek.yaml (via an alias) and haven't seen any problems (yet). It's also 0.8 seconds faster, 2.6s compared to 3.4s without (and 2.1s faster than pre-commit's 4.7s).

    ❯ hyperfine --warmup 1 --runs 3 \
      "prek run --all-files -c .prek.yaml" \
      "prek run --all-files" \
      "pre-commit run --all-files"
    Benchmark 1: prek run --all-files -c .prek.yaml
      Time (mean ± σ):      2.606 s ±  0.003 s    [User: 13.918 s, System: 4.674 s]
      Range (min … max):    2.602 s …  2.608 s    3 runs
    
    Benchmark 2: prek run --all-files
      Time (mean ± σ):      3.396 s ±  0.144 s    [User: 13.855 s, System: 5.581 s]
      Range (min … max):    3.231 s …  3.500 s    3 runs
    
    Benchmark 3: pre-commit run --all-files
      Time (mean ± σ):      4.728 s ±  0.330 s    [User: 18.064 s, System: 4.610 s]
      Range (min … max):    4.409 s …  5.067 s    3 runs
    
    Summary
      prek run --all-files -c .prek.yaml ran
        1.30 ± 0.06 times faster than prek run --all-files
        1.81 ± 0.13 times faster than pre-commit run --all-files
    

    Anyway, we can also reconsider adding priority in the future when switching to prek in the Makefile.

  12. added a commit that references this issue on Feb 15, 2026
  13. vivodi commented on Mar 23, 2026

    @vivodi
    Contributor

    I recommend migrating directly to the pre-commit.ci GitHub app instead of the approach in this issue.

    Running prek within GitHub Actions lacks persistent caching, forcing CI to re-download and reinstall all hooks from scratch on every run. In contrast, the pre-commit.ci GitHub app caches environments efficiently, ensuring time is spent only on executing hooks rather than setup. This would be significantly faster than both existing options.

    I opened the issue #146317 Replace prek GitHub Action with pre-commit.ci GitHub app for better performance for this.

  14. j178 commented on Mar 23, 2026

    @j178
    Contributor

    Running prek within GitHub Actions lacks persistent caching, forcing CI to re-download and reinstall all hooks from scratch on every run.

    @vivodi Did you check that? Where’s that claim coming from?

    I just looked at the logs from the latest lint job:
    https://github.com/python/cpython/actions/runs/23422492043/job/68130457779

    From what I can see, prek-action did reuse the cached hooks and didn’t reinstall them.

    Cache hit for: prek-v1|Linux|X64||00ab2e29f83edd65b54f623a840eb7eaac02f3410a1103a298c9f5e0cfd9fb1c
    Received 87066013 of 87066013 (100.0%), 113.1 MBs/sec
    Cache Size: ~83 MB (87066013 B)
    /usr/bin/tar -xf /home/runner/work/_temp/44d88d24-8d1d-4b2e-9a1a-d22fd1a7d932/cache.tzst -P -C /home/runner/work/cpython/cpython --use-compress-program unzstd
    Cache restored successfully
    Cache restored from key: prek-v1|Linux|X64||00ab2e29f83edd65b54f623a840eb7eaac02f3410a1103a298c9f5e0cfd9fb1c
    
      2026-03-23T05:12:52.889535Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889547Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889550Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889554Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889557Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889561Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889564Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889568Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889571Z DEBUG Found installed environment for hook `ruff-check` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889574Z DEBUG Found installed environment for hook `ruff-format` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889578Z DEBUG Found installed environment for hook `ruff-format` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889581Z DEBUG Found installed environment for hook `ruff-format` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889585Z DEBUG Found installed environment for hook `ruff-format` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889588Z DEBUG Found installed environment for hook `ruff-format` at `/home/runner/.cache/prek/hooks/python-lxWWQdrVxM97y9gmhlYn`
      2026-03-23T05:12:52.889593Z DEBUG Found installed environment for hook `black` at `/home/runner/.cache/prek/hooks/python-mSN0r7l4SMxCSpFUuEhx`
      2026-03-23T05:12:52.889597Z DEBUG Found installed environment for hook `remove-tabs` at `/home/runner/.cache/prek/hooks/python-RBUuRr1uTN0ATKQuH0N6`
      2026-03-23T05:12:52.889607Z DEBUG Found installed environment for hook `check-case-conflict` at `/home/runner/.cache/prek/hooks/python-aZBFktYSFEwszQ3BaFhr`
      2026-03-23T05:12:52.889611Z DEBUG Found installed environment for hook `check-merge-conflict` at `/home/runner/.cache/prek/hooks/python-aZBFktYSFEwszQ3BaFhr`
      2026-03-23T05:12:52.889615Z DEBUG Found installed environment for hook `check-toml` at `/home/runner/.cache/prek/hooks/python-aZBFktYSFEwszQ3BaFhr`
      2026-03-23T05:12:52.889618Z DEBUG Found installed environment for hook `check-yaml` at `/home/runner/.cache/prek/hooks/python-aZBFktYSFEwszQ3BaFhr`
      2026-03-23T05:12:52.889621Z DEBUG Found installed environment for hook `end-of-file-fixer` at `/home/runner/.cache/prek/hooks/python-aZBFktYSFEwszQ3BaFhr`
      2026-03-23T05:12:52.889625Z DEBUG Found installed environment for hook `end-of-file-fixer` at `/home/runner/.cache/prek/hooks/python-aZBFktYSFEwszQ3BaFhr`
      2026-03-23T05:12:52.889628Z DEBUG Found installed environment for hook `trailing-whitespace` at `/home/runner/.cache/prek/hooks/python-aZBFktYSFEwszQ3BaFhr`
      2026-03-23T05:12:52.889632Z DEBUG Found installed environment for hook `trailing-whitespace` at `/home/runner/.cache/prek/hooks/python-aZBFktYSFEwszQ3BaFhr`
      2026-03-23T05:12:52.889636Z DEBUG Found installed environment for hook `check-dependabot` at `/home/runner/.cache/prek/hooks/python-IjRmuA7wrzrJpNj8HAx1`
      2026-03-23T05:12:52.889640Z DEBUG Found installed environment for hook `check-github-workflows` at `/home/runner/.cache/prek/hooks/python-IjRmuA7wrzrJpNj8HAx1`
      2026-03-23T05:12:52.889643Z DEBUG Found installed environment for hook `check-readthedocs` at `/home/runner/.cache/prek/hooks/python-IjRmuA7wrzrJpNj8HAx1`
      2026-03-23T05:12:52.889647Z DEBUG Found installed environment for hook `zizmor` at `/home/runner/.cache/prek/hooks/python-ufsx3xz1oK0OYZO5eNzi`
      2026-03-23T05:12:52.889651Z DEBUG Found installed environment for hook `sphinx-lint` at `/home/runner/.cache/prek/hooks/python-cRoa1LI13h9MPgizrWrl
    
  15. vivodi commented on Mar 23, 2026

    @vivodi
    Contributor

    I just tested it—running via the pre-commit.ci GitHub app takes about 38 seconds, which is roughly the same as running prek in GitHub Actions.

    Sorry for the confusion earlier, and apologies for the noise.

    Image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    infraCI, GitHub Actions, buildbots, Dependabot, etc.type-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions