ci(prek): quieten the CI log — drop --verbose, skip the identity hook - #1165
Merged
Conversation
Two sources of noise around the one thing a reader opens the log for. `identity` is a meta-hook that echoes every file passed to the static checks. Useful locally when working out why a hook did or did not fire; on `--all-files` it prints the entire repository into the log before any result appears. It stays enabled locally and is skipped only in CI. `--verbose` was added on the belief that it bought live progress in a non-TTY log. Measured, it does not: first output and total runtime coincide with the flag and without it, so prek emits nothing until the run finishes either way. It streams only when stdout is a TTY, where it draws an in-place progress UI with cursor-control escapes; against a pipe it suppresses that renderer and prints one block at the end. `--no-progress` behaves identically and `-q` prints nothing at all. The flag's only effect in CI was to add every passing hook's stdout to that final block. Failures still print their output, and `--show-diff-on-failure` still shows what a fixer hook changed. Note for anyone editing this later: `--skip a,b` is not a list. Prek takes one HOOK|PROJECT per occurrence and merely warns that a comma-joined value matched no hooks, so the comma form reads as working while skipping nothing — which is what my first attempt did, caught by running the command rather than trusting it. Generated-by: Claude Code (Opus 5)
potiuk
force-pushed
the
ci-prek-quieter
branch
from
September 8, 2026 02:52
ca7cb67 to
9acef42
Compare
Experiment to validate on the runner: prek streams per-hook results only when stdout is a TTY. `script -qec` allocates one; `--no-progress` suppresses the spinner whose in-place redraws would otherwise fill the log with cursor-control escapes. `-e` propagates the exit status, without which a failing prek would report as a passing step. Cannot be verified locally — script(1) has no terminal to attach to in this sandbox — so this is measured against the real Actions log and kept or reverted on that evidence. Generated-by: Claude Code (Opus 5)
pty plus --no-progress streamed nothing. `--no-progress` hides the streaming renderer itself, not merely its spinner — the progress rendering and the live per-hook output are the same mechanism, so suppressing one suppresses the other. That combination paid the pty's cost and bought none of its benefit. Keeping the pty alone: prek streams, at the price of the in-place redraw escapes the spinner emits. Generated-by: Claude Code (Opus 5)
Measured on the runner, not assumed. Three configurations, each with every hook-result line landing in the same one-second timestamp roughly 106s into the step: plain pipe one block at the end pty (script -qec) one block at the end pty + --no-progress one block at the end A pty does make prek stream locally — spinner, in-place redraws — so the renderer itself works. That output just does not survive to the Actions log, which shows only the final state. PYTHONUNBUFFERED was never in the path: prek is a Rust binary and captures each hook's stdout to replay after the hook finishes. So the plain form comes back: identical information, no script(1) dependency, and no -e footgun, where script would otherwise return its own exit status and a failing prek would report as a passing step. --skip identity and the removal of --verbose stand on their own evidence and remain. Generated-by: Claude Code (Opus 5)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two sources of noise in the
prekCI log, around the one thing a reader opensthat log for.
--skip identity. Theidentitymeta-hook echoes every file passed to thestatic checks. Useful locally when working out why a hook did or did not fire,
but on
--all-filesit prints the entire repository into the log before anyresult appears. It stays enabled locally and is skipped only in CI.
Drop
--verbose— see below, because the reason it was there turns out notto hold.
--verbosenever bought live progressThe flag carried a comment saying that without it, prek renders a progress UI a
non-TTY log buffers to the end. The first half is true; the conclusion is not.
Measured on this repo, timestamping each line and comparing against total
runtime:
--verbose--no-progress-qFirst output and total runtime coincide in every mode. prek emits nothing
until the run finishes, with or without the flag.
It streams only when stdout is a TTY, where it draws an in-place progress UI
— spinner,
Running hooks..., cursor-control escapes redrawing lines. Against apipe (CI) it suppresses that renderer and prints one block at the end. Confirmed
by running it under
scriptto allocate a pty, which does stream — and fills theoutput with
ESC[2K/ESC[2Aredraw sequences.So in CI the flag's only effect was to add every passing hook's stdout to that
final block: several hundred lines wrapped around nothing. Failures still print
their output, and
--show-diff-on-failurestill shows what a fixer hook changed.Real streaming would mean allocating a pty in the workflow (
script -qec …),which trades a clean block for a log full of ANSI redraw escapes. Waiting for the
block is the better trade.
For completeness:
PYTHONUNBUFFEREDdoes not apply — prek is a Rust binary, andit captures each hook's stdout and replays it after the hook completes, so a
Python hook's own buffering is not in the path either.
A trap worth documenting
My first attempt wrote
--skip workspace-pytest,identity. Prek takes a singleHOOK|PROJECTper occurrence, so a comma-joined value matches no hook — and itonly warns:
then runs
identityanyway. The comma form reads as working while skippingnothing. Caught by running the command instead of trusting the edit; the workflow
now carries a note so the next person does not repeat it.
Why
workspace-pyteststays skipped (unchanged behaviour).github/workflows/tests.ymlalready runs pytest as a data-driven matrix — oneparallel job per workspace member, named after the member, so a failure points at
the culprit. The
workspace-pytesthook runs the same tests bundled seriallyinside the
prekjob, where a failure is buried. Path filtering on PRs is not ahole: four shared build inputs (
pyproject.toml,uv.lock,tools/dev/run-workspace-check.sh,tests.yml) force the full matrix, andnon-PR events always run everything. The hook still runs locally on
git commit/prek run, where there is no matrix to defer to.Test plan
identityabsentbuffering behaviour above, rather than asserting it
An earlier revision of this PR claimed per-hook lines "still stream" without
--verbose. That was wrong — they never streamed in CI either way — and thecommit and workflow comment have been corrected.
🤖 Generated with Claude Code
https://claude.ai/code/session_01So3JRGXrbqSGrohtZuHWKg