A sieve that was not in the flow

skill/redact-secrets, #3725, fixed by an outside contributor in #3815 · one read order, one honest test · 2026-08-30

The one line

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 || '';

CC sends tool_response on PostToolUse (types.ts, #3418, from the decompiled payload builder). tool_result and output are legacy aliases no real payload carries. So toolOutput was empty on every invocation, the hook returned silent success, and every pattern in this layer, the #3589 GitLab family included, had never matched production output. Its sibling posttool/secret-handler.ts had read tool_response first all along.

Send a payload: which field is it in?

readerbefore #3815after #3815
redact-secrets sees the token??
the unit suite (fixtures built with the same field)??

Three sieves, one held sideways

output from a tool
        |
        v
  [ secret-handler ]      reads tool_response          catches   (bounded in-place redaction)
        |
        v
  [ redact-secrets ]      read tool_result -> ''       PASSED EVERYTHING THROUGH     <- #3725
        |
        v
  [ crypto.sanitizePayload ]  telemetry payloads        catches

  the GitLab patterns added in #3589 went into all three; the middle one was decorative

The test that earns the merge

runresultwhat it proves
PR head: redact-secrets.test.ts + new integration test70 / 70two unit cases (reads tool_response; prefers it over tool_result) and three integration cases through the BUILT bundle
the same integration test against MAIN's plugins/ork/hooks/dist/skill.mjs2 fail / 1 passthe two detection cases fail, the clean-output case passes: it measures the fix, not the mock
npm run typecheck on PR headcleanno type drift

The side-finding: every hook no-ops on Windows

Kept out of scope by the contributor, correctly. run-hook.mjs imports its dist bundle by bare absolute path; on win32 that is not a valid ESM specifier, Node throws ERR_UNSUPPORTED_ESM_URL_SCHEME, and the runner's catch maps it to {"continue":true} before stdin is read. The Windows smoke test is green, which means it asserts exit codes, not verdicts. Tracked as #3817 with a fix shape: pathToFileURL, a catch that does not certify a bundle that failed to load, and a Windows probe that expects a deny.