Skip to content

ci(prek): quieten the CI log — drop --verbose, skip the identity hook - #1165

Merged
potiuk merged 4 commits into
apache:mainfrom
potiuk:ci-prek-quieter
Sep 8, 2026
Merged

ci(prek): quieten the CI log — drop --verbose, skip the identity hook#1165
potiuk merged 4 commits into
apache:mainfrom
potiuk:ci-prek-quieter

Conversation

@potiuk

@potiuk potiuk commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

Two sources of noise in the prek CI log, around the one thing a reader opens
that log for.

-  prek run --show-diff-on-failure --color=always --all-files
-  --verbose --skip workspace-pytest
+  prek run --show-diff-on-failure --color=always --all-files
+  --skip workspace-pytest --skip identity

--skip identity. The identity meta-hook echoes every file passed to the
static checks. Useful locally when working out why a hook did or did not fire,
but 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.

Drop --verbose — see below, because the reason it was there turns out not
to hold.

--verbose never bought live progress

The 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:

Invocation First output Total
plain 19s 19s
--verbose 19s 20s
--no-progress 19s 19s
-q (no output) 18s

First 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 a
pipe (CI) it suppresses that renderer and prints one block at the end. Confirmed
by running it under script to allocate a pty, which does stream — and fills the
output with ESC[2K / ESC[2A redraw 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-failure still 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: PYTHONUNBUFFERED does not apply — prek is a Rust binary, and
it 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 single
HOOK|PROJECT per occurrence, so a comma-joined value matches no hook — and it
only warns:

warning: selector `--skip=workspace-pytest,identity` did not match any hooks

then runs identity anyway. The comma form reads as working while skipping
nothing. 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-pytest stays skipped (unchanged behaviour)

.github/workflows/tests.yml already runs pytest as a data-driven matrix — one
parallel job per workspace member, named after the member, so a failure points at
the culprit. The workspace-pytest hook runs the same tests bundled serially
inside the prek job, where a failure is buried. Path filtering on PRs is not a
hole: four shared build inputs (pyproject.toml, uv.lock,
tools/dev/run-workspace-check.sh, tests.yml) force the full matrix, and
non-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

  • Ran the exact post-change CI command locally — exit 0, identity absent
  • Timestamped every output line across four invocations to establish the
    buffering behaviour above, rather than asserting it
  • Confirmed under a pty that streaming exists but costs ANSI redraw escapes
  • Confirmed the comma form silently skips nothing

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 the
commit and workflow comment have been corrected.

🤖 Generated with Claude Code

https://claude.ai/code/session_01So3JRGXrbqSGrohtZuHWKg

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)
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)
@potiuk
potiuk merged commit fccfe65 into apache:main Sep 8, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant