Route OPF Progress to stderr · Entire
Why you see this output
The lines come from the redact package's OPF progress UX (redact/opf.go, redact/batch.go, redact/redact.go). The key detail is where they're written:
var opfStderr = openTTYOrDiscard() // opens /dev/tty, falls back to io.Discard
opfStderr is wired to /dev/tty, not stdout or stderr. That's deliberate for production: the post-commit hook runs with 2>/dev/null, so writing to the controlling terminal directly lets the "scanning…/done" progress survive that redirect.
The side effect: because it goes straight to your terminal, it bypasses go test's output capture and shows up interleaved with PASS lines.
The tests producing it are the strategy-package OPF tests (manual_commit_opf_rewrite_test.go and friends). They configure a fake OPF runtime via configureFakeOPF:
redact.ConfigurePrivacyFilterWithRuntime(redact.OPFConfig{
Enabled: true, Categories: {"private_person": true}, Command: "/tmp/test-opf",
}, rt) // rt is an in-memory fake
…but unlike the redact package's own tests (which set opfStderr = io.Discard), these tests never silence opfStderr. So the progress messages leak to /dev/tty. The × unavailable (/tmp/test-opf) line is the fail-closed test exercising the "OPF binary missing → fall back to regex layers" path; /tmp/test-opf is a deliberately bogus command path.
Is it doing anything expensive?
No. Everything you saw is cheap, in-memory work:
- The runtime is a fake stub —
RedactBatchjust returns precomputed spans (e.g.findSentinelSpans). No subprocess, no Python/opfbinary, no model load, no network. The0.0stimings confirm it. - The
pip install opf/unavailableline is a fallback path — it does less (skips OPF, runs only regex layers over a few tiny test blobs). - Real OPF (what users get when
redaction.openai_privacy_filter.enabledis true) shells out to a Python process and can take ~30s — that's why the real pre-push prompt warns "may take ~30s." But that real path is never hit here.
So this is harmless cosmetic leakage, not a performance concern.
If you want to silence it
The clean fix is to make configureFakeOPF redirect opfStderr to io.Discard for the duration of the test (matching what redact's own tests already do), via a small exported test helper in redact — there's currently no exported setter, only the unexported var.
Summary
- Edge case the
/dev/ttytrick still covers: if a future caller runs OPF (usePrivacyFilter=true) from a context whose stderr is redirected — e.g. someone adds an OPF pass back into post-commit, or a wrapper that swallows hook stderr. Today nothing does, so this is purely hypothetical.
Confirmed Changes
- OPF no longer runs at post-commit — it lives solely in the pre-push rewrite path, whose hook is installed without a stderr redirect.
- Switching
opfStderrfromopenTTYOrDiscard()toos.Stderrwould actually be a net improvement:- pre-push: still visible (stderr isn't swallowed there).
- tests: the progress would go to
go test's captured stderr instead of bleeding straight to the terminal.
This is local to the redact/strategy area and unrelated to your clean working tree's prior state.