redact: route OPF progress to stderr instead of /dev/tty · Entire
redact: route OPF progress to stderr instead of /dev/tty
626a034→main·Soph·4w ago·4 files·+31 added/-52 removed
OPF progress UX (→ scanning / ✓ done / × unavailable) defaulted to /dev/tty so the messages survived the post-commit hook's 2>/dev/null redirect. OPF no longer runs at post-commit — it lives solely in the pre-push rewrite path, whose hook is installed without a stderr redirect (post-commit condensation calls the 7-layer functions directly via RedactBlobBytes(..., usePrivacyFilter=false)). So the /dev/tty routing is obsolete: plain os.Stderr is visible during git push and, in tests, is captured by go test instead of bleeding straight to the terminal.
- redact/opf.go: default opfStderr to os.Stderr; drop openTTYOrDiscard.
- redact/global_test.go: silence opfStderr once in TestMain, matching how the strategy package handles its pre-push progress writer.
- redact/{opf,batch}_test.go: remove the 11 per-test io.Discard overrides (now redundant, and race-free as a single set-once).
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
8cc824aa79c0View transcript
Changes
4
- redact
- Mbatch_test.go+4/-13
- Aglobal_test.go+18
- Mopf.go+9/-13
- Mopf_test.go-26
2 unmodified lines
3
4
5
6
6
7
8
9
11
12
13
10
11
12
13
14
15
16
17
18
19
20
18
19
20
134 unmodified lines
155
156
157
161
162
163
158
159
160
94 unmodified lines
255
256
257
264
265
266
258
259
260
2 unmodified lines
import (
"context"
"errors"
"io"
"strings"
"testing"
)
// configureFakeOPF wires up the runtime, redirects opfStderr, and registers
// cleanup so each test starts and ends with a clean config. Returns the fake
// so individual tests can assert call counts.
// configureFakeOPF wires up the runtime and registers cleanup so each
// test starts and ends with a clean config. Returns the fake so
// individual tests can assert call counts. (opfStderr is silenced
// process-wide in TestMain; see global_test.go.)
func configureFakeOPF(t *testing.T, fake *fakeRuntime, cats map[string]bool) {
t.Helper()
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
ConfigurePrivacyFilterWithRuntime(OPFConfig{
Enabled: true,
Categories: cats,
134 unmodified lines
rt := &recordingRuntime{spans: []Span{{Start: 0, End: 5, Label: "private_person"}}}
resetOPFConfig()
...
ConfigurePrivacyFilterWithRuntime(OPFConfig{
Enabled: true,
Categories: map[string]bool{"private_person": true},
94 unmodified lines
fake := &shortReturnBatchRuntime{}
resetOPFConfig()
...
}
Mredact/batch_test.go+4/-13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package redact
import (
"io"
"os"
"testing"
)
func TestMain(m *testing.M) {
// OPF progress UX (→ scanning / ✓ done / × unavailable) defaults to
// os.Stderr in production. Silence it process-wide for tests so it
// neither bleeds into `go test -v` output nor needs a per-test
// override. No test captures opfStderr to assert on its content; the
// strategy package suppresses its own pre-push progress writer the
// same way (see strategy/global_test.go).
opfStderr = io.Discard
os.Exit(m.Run())
}
Aredact/global_test.go+18
180 unmodified lines
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
184
185
186
187
188
189
190
191
192
193
194
195
180 unmodified lines
return out
}
// opfStderr is where progress and failure UX is written. Defaults to
// /dev/tty so messages survive the post-commit hook's stderr redirect
// (the hook runs `entire hooks git post-commit 2>/dev/null` to suppress
// unrelated logging). Tests override this directly.
var opfStderr = openTTYOrDiscard()
func openTTYOrDiscard() io.Writer {
f, err := os.OpenFile("/dev/tty", os.O_WRONLY, 0)
if err != nil {
return io.Discard
}
return f
}
// opfStderr is where progress and failure UX is written. OPF only runs
// in the pre-push rewrite path (strategy/manual_commit_opf_rewrite.go),
// whose hook is installed without an `2>/dev/null` redirect, so plain
// stderr reaches the user's terminal during `git push`. Post-commit
// condensation never invokes OPF (it calls the 7-layer functions
directly via RedactBlobBytes(..., usePrivacyFilter=false)), so the
// historical `/dev/tty` routing that survived the post-commit hook's
// stderr redirect is no longer needed. Tests override this directly.
var opfStderr io.Writer = os.Stderr
// detectOPF runs OPF on a single text and returns tagged regions for any
// spans whose category is enabled in cfg. Returns nil when OPF is disabled,
Mredact/opf.go+9/-13
2 unmodified lines
3
4
5
6
6
7
8
120 unmodified lines
129
130
131
133
134
135
136
132
133
134
23 unmodified lines
158
159
160
166
167
168
161
162
163
46 unmodified lines
210
211
212
221
222
223
213
214
215
45 unmodified lines
261
262
263
275
276
277
264
265
266
263 unmodified lines
530
531
532
547
548
549
533
534
535
10 unmodified lines
546
547
548
566
567
568
549
550
551
17 unmodified lines
569
570
571
592
593
594
572
573
574
40 unmodified lines
615
616
617
641
642
643
618
619
620
2 unmodified lines
import (
"context"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
120 unmodified lines
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
fake := &fakeRuntime{
spans: []Span{
{Start: 0, End: 5, Label: "private_person"},
23 unmodified lines
func TestDetectOPF_SkipsCategoriesNotEnabled(t *testing.T) {
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
fake := &fakeRuntime{
spans: []Span{
46 unmodified lines
func TestDetectOPF_CircuitBreaker(t *testing.T) {
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
fake := &fakeRuntime{err: errors.New("simulated opf failure")}
ConfigurePrivacyFilterWithRuntime(OPFConfig{
45 unmodified lines
func TestDetectOPF_SingleInputShortReturnTripsBreaker(t *testing.T) {
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
ConfigurePrivacyFilterWithRuntime(OPFConfig{
Enabled: true,
263 unmodified lines
func TestStringWithPrivacyFilter_AugmentsRegexLayers(t *testing.T) {
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
fake := &fakeRuntime{spans: []Span{{Start: 0, End: 5, Label: "private_person"}}}
ConfigurePrivacyFilterWithRuntime(OPFConfig{
10 unmodified lines
func TestJSONLContentWithPrivacyFilter_BatchesSingleCall(t *testing.T) {
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
fake := &fakeRuntime{spans: []Span{{Start: 0, End: 5, Label: "private_person"}}}
ConfigurePrivacyFilterWithRuntime(OPFConfig{
17 unmodified lines
func TestJSONLContentWithPrivacyFilter_FallsBackOnBatchError(t *testing.T) {
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
fake := &fakeRuntime{err: errors.New("simulated opf failure")}
ConfigurePrivacyFilterWithRuntime(OPFConfig{
40 unmodified lines
func TestJSONLContentWithPrivacyFilter_ShortReturnTripsBreaker(t *testing.T) {
resetOPFConfig()
t.Cleanup(resetOPFConfig)
origStderr := opfStderr
opfStderr = io.Discard
t.Cleanup(func() { opfStderr = origStderr })
ConfigurePrivacyFilterWithRuntime(OPFConfig{
Enabled: true,