Review of #3815

fix(hooks): redact-secrets reads tool_response, the field CC sends. Fork PR by a first-time contributor, fixes your own #3725. Verdict: correct, minimal, honestly tested. Two maintainer chores at landing.

1 lineof production code changed
70 / 70unit + integration, PR head, run here
2 / 3 failits new test against main's bundle (real control)
14CI runs were held; approved after the read
01 · the diff

The whole fix is one read order

Verified against main, not the PR description: redact-secrets.ts:33 reads only the legacy aliases; secret-handler.ts:60 already reads tool_response first.

src/hooks/src/skill/redact-secrets.ts

- const toolOutput = (input as any).tool_result || (input as any).output || '';
+ const toolOutput =
+   (input as any).tool_response || (input as any).tool_result || (input as any).output || '';

Same precedence as posttool/secret-handler.ts. types.ts documents tool_response as the field CC actually sends (#3418, from the decompiled payload builder).

What it meansThe third redaction layer starts scanning real output for the first time.
If we do nothingEvery pattern in that layer, GitLab family included, keeps matching nothing.
02 · the proof

Its own test fails without the fix. I checked.

Ran on a worktree at the PR head, then copied its integration test into main's tree and ran it against main's built bundle.

runresultwhat it says
npm run typecheck on PR headcleanno type drift from the as any read
redact-secrets.test.ts + new integration test, PR head70 / 70two new unit cases (reads tool_response; prefers it over tool_result) plus 3 integration cases
same integration test against main's plugins/ork/hooks/dist/skill.mjs2 fail / 1 passthe two detection cases fail, the clean-output case passes: the test measures the fix, not the mock

The integration test drives the built bundle two ways: spawning bin/run-hook.mjs with a CC-shaped PostToolUse payload (asserts exit 0, no token in stdout, the ::warning:: on stderr) and importing dist/skill.mjs in-process. It skips itself when the bundle is absent.

What it meansThis is the #3801 / #3804 class caught by the right kind of test.
If we do nothingNothing; the evidence is already in the PR.
03 · the shape of the bug

Three sieves, one held sideways

ork has three redaction layers. This one read a field production never fills, so everything passed straight through it while its unit suite fed it the field it wanted.

secret-handler reads tool_response catches redact-secrets read tool_result, always empty everything fell past it crypto.sanitizePayload telemetry payloads catches
What it means#3589's GitLab patterns were added to a sieve that was not in the flow.
If we do nothingThe other two layers still catch; this one stays decorative.
04 · the safety read

Seven files, nothing that reaches outside the repo

Read before any of its CI was allowed to run. Additions were also grepped for fetch, URLs, curl, secret-bearing env reads and shell spawns.

filekindnote
src/hooks/src/skill/redact-secrets.tssourcethe one-line read order, with a comment naming #3725
src/hooks/src/__tests__/skill/redact-secrets.test.tstest+2 cases, same fixture style as the file
src/hooks/src/__tests__/integration/redact-secrets-tool-response.test.tstestnew; only spawn is node bin/run-hook.mjs (ours); scratch dir under tmp; existing integration/ dir is collected by vitest's src/__tests__/** include
src/hooks/dist/*, plugins/ork/hooks/dist/*generated17-byte bundle delta, exactly the new read; bundle-stats follow

Contributor: first PR in this repo. Cross-repository fork, maintainerCanModify is on. No workflow files touched. Risky-string scan of additions: only the docstring mentioning the runner.

What it meansSafe to run and safe to merge on content.
If we do nothingCI is already running; the PR just waits for a decision.
05 · the side-finding

They also found that every hook no-ops on Windows

run-hook.mjs does import(<absolute dist path>); on win32 that throws ERR_UNSUPPORTED_ESM_URL_SCHEME and the surrounding catch turns it into silent success before stdin is read.

claimstatuswhy it matters
win32 absolute path in import() is rejected by Nodeplausible, unverified hereNode requires file:// URLs on Windows; a bare C:\... path is exactly the documented failure
the runner's catch maps it to {"continue":true}matches the runner's shapethe same fail-open the verdict probes exist to catch; the Windows Smoke Test would not see it if it never asserts a verdict

Kept out of scope by the contributor, correctly. It needs its own issue with a probe that asserts a verdict on Windows, not just exit 0.

What it meansA second dead-hook class, platform-shaped this time, is on the table.
If we do nothingWindows users run a plugin whose hooks all silently pass.
06 · your call

How do you want it landed?

Content is approved. The repo's own conventions (registry changelog entry, Lab playground) are maintainer chores a stranger should not be taxed for.

after you pick:


    

So what?

A stranger found a dead sieve you had already named, fixed it with the smallest possible change, and proved it with a test that fails on main. The only open question is who adds the two lines of house paperwork.