Skip to content

fix(core): redact error text in usage-statistics telemetry sink - #11649

Merged
yiliang114 merged 8 commits into
mainfrom
fix/11198-telemetry-error-redaction
Sep 12, 2026
Merged

yiliang114 merged 8 commits into
mainfrom
fix/11198-telemetry-error-redaction

Conversation

@yiliang114

@yiliang114 yiliang114 commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Usage-statistics telemetry forwards raw tool and provider error text to the RUM endpoint. This PR replaces the known free-form error fields (error_message, error_excerpt, and top-level message) with a fixed ***REDACTED*** marker at the single enqueue boundary before an event can leave the process. (The hook error property is removed at its producer logHookCallEvent rather than marker-replaced, so hook_call#*.properties.error is no longer collected at all.)

Why it's needed

A failed shell command can contain a credential in a URL, an authorization header, a flag, an environment assignment, truncated output, or an unanticipated form. Parsing that untrusted text with a growing regex denylist cannot prove that the credential is gone and can itself become a CPU denial-of-service path. Replacing the whole error string is smaller and fails closed.

Reviewer Test Plan

How to verify

Run the focused unit suite:

cd packages/core && npx vitest run src/telemetry/qwen-logger/qwen-logger.test.ts

The regression drives the public enqueue boundary with all currently known error-text properties plus a top-level exception message. It asserts that every raw value is replaced while a non-text classification field is unchanged.

Evidence (Before & After)

Before: an event whose error text contained git clone https://x-access-token:ghs_testsecret123@github.com/... was enqueued with the complete command and token.

After: the entire error-text field is enqueued as ***REDACTED***; no input substring is retained or parsed.

Tested on

OS Status
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Unit tests only — npx vitest run inside packages/core.

Risk & Scope

The implementation now replaces whole error fields, superseding the earlier shape-based masking proposal recorded on #11198. The privacy/diagnostics tradeoff still needs maintainer confirmation; this PR must not automatically close that issue before the decision is recorded.

  • Main risk or tradeoff: usage-statistics events retain the fact that an error occurred and their structured classifications, but no longer retain free-form error diagnostics. This is deliberate because the channel is default-on and any partial text policy can leak credentials.
  • Not validated / out of scope: snapshots and stack are separate structured fields and are not changed here; any sensitive producer-to-sink path through them should be handled separately.
  • Breaking changes / migration notes: none for the public API; backend users must rely on structured error fields instead of raw text.

Linked Issues

Refs #11198

中文说明

本 PR 做了什么

usage-statistics 遥测会把工具和 provider 的原始错误文本发送到 RUM endpoint。本 PR 在事件离开进程前的唯一入队边界,将当前已知的自由文本错误字段(error_message、error_excerpt 和顶层 message)统一替换为固定的 ***REDACTED*** 标记。(hook 的 error 属性在其生产者 logHookCallEvent 处被移除、而非替换为标记,因此 hook_call#*.properties.error 不再被采集。)

为什么需要它

失败的 shell 命令可能以 URL、Authorization header、命令参数、环境变量、截断输出或任意其他形式携带凭据。不断扩展正则黑名单无法证明凭据已被完整清除,还会为不可信错误文本引入 CPU 拒绝服务风险。整段替换更小,也能失败关闭。

Reviewer 测试计划

如何验证

运行聚焦的单元测试:

cd packages/core && npx vitest run src/telemetry/qwen-logger/qwen-logger.test.ts

回归测试直接走公开的入队边界,覆盖当前所有已知错误文本属性和顶层异常消息;它断言原始值全部被替换,同时非文本分类字段保持不变。

证据(Before & After)

Before:错误文本包含 git clone https://x-access-token:ghs_testsecret123@github.com/... 的事件,会带着完整命令和 token 入队。

After:整段错误文本被替换为 ***REDACTED***,不保留也不解析任何输入片段。

测试环境

操作系统 状态
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

仅单元测试——在 packages/core 内运行 npx vitest run。

风险与范围

当前实现替换整个错误字段,已不同于 #11198 先前记录的按内容模式遮罩方案。隐私与诊断信息之间的取舍仍需维护者确认,因此本 PR 不应在决策记录之前自动关闭该 issue。

  • 主要风险或取舍:usage-statistics 事件仍保留“发生错误”的事实和结构化分类,但不再保留自由文本诊断信息。该取舍是有意的,因为此通道默认开启,任何保留部分文本的策略都可能泄漏凭据。
  • 未验证 / 超出范围:snapshots 和 stack 是独立的结构化字段,本 PR 不修改;如果它们存在敏感的 producer-to-sink 路径,应单独处理。
  • 破坏性变更 / 迁移说明:公共 API 无变化;后端分析应依赖结构化错误字段,而不是原始错误文本。

关联 Issue

Refs #11198

@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Sep 11, 2026
@yiliang114
yiliang114 force-pushed the fix/11198-telemetry-error-redaction branch from 887b430 to 7df466e Compare September 11, 2026 10:05
@github-actions

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

Close the highest-severity review findings on the redaction table:

- Add `error` to ERROR_TEXT_PROPERTY_KEYS so hook failure text written to
  properties['error'] is redacted at the enqueue choke point (R1-1).
- Skip any auth scheme (Basic/token/Digest/…) before the value, not only
  Bearer, so `Authorization: Basic <creds>` no longer leaks (R1-2).
- Add `key` to the env key alternation so *_API_KEY / *_ACCESS_KEY* are
  redacted (R1-6).
- Run redaction on the raw text before stripAnsiAndControl so the
  whitespace-delimited patterns still see their newline/tab delimiters (R1-7).
- Share one secret-value pattern that accepts a quote-opened value (R1-3) and
  let the flag name contain the secret keyword anywhere (R1-4).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtwznj86xz
yiliang114 and others added 2 commits September 11, 2026 22:46
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtx1sp0ry2
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtx4nkoby7
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

yiliang114 and others added 2 commits September 12, 2026 04:57
Harden the usage-statistics error-text redaction pass against the review
findings on this branch:

- Normalise shell line continuations (`\` + newline + leading whitespace)
  ahead of every pass, so a credential split across a continuation (including
  a URL credential) can't survive as a glued cleartext run.
- Make SECRET_VALUE quote-aware (a quoted run matches to its closing quote),
  so interior quotes and quoted values containing spaces redact whole instead
  of leaking a suffix after the marker.
- Give the env pattern the same quoted alternative behind its floor.
- Skip an optional leading backslash in each separator so a shell-escaped
  nested quote (`Authorization: Bearer \"secret\"`) still redacts.
- Strip ANSI/C0 per line (after widening tabs to spaces) before the patterns
  run, so an escape or control character can't hide or reassemble a credential
  the pattern pass never saw.

Fix the line-continuation test to assert the secret is absent rather than
merely that no marker is emitted, and add regression cases for interior
quotes, quoted values with spaces, ANSI/C0, and continuations.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtxennouyn
- Bound the non-secret name chars around the secret keyword in
  SECRET_FLAG_PATTERN and ENV_SECRET_PATTERN (previously unbounded `*`,
  now `{0,64}`) to prevent quadratic backtracking on adversarial error
  text that runs through the synchronous telemetry redaction pass.
- Give SECRET_FLAG_PATTERN the same 10-char value floor as
  ENV_SECRET_PATTERN so `--max-tokens 8192` and `max_tokens=8192` are
  handled consistently (both pass through untouched).
- Align the ENV_SECRET_PATTERN docblock examples with the actual 10-char
  floor (the old `ghs_xxx` / `sk_xxx` examples were shorter than the floor
  and were never redacted).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtxgstgfyr
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

2 similar comments
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@yiliang114

yiliang114 commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Closeout at ea640b45c2fd: no source changes or new verification run in this pass. The current two-file diff is smaller than the initial shape parser (implementation: 64 → 25 changed lines; tests: 86 → 73). The ledger continues from five substantive rounds at c6294830 to six after the hook producer was removed; this evidence-only pass does not add a round.

R5-1 is stale: both language versions of the PR description now say error_message / error_excerpt / top-level message are marker-replaced, while hook error is no longer collected. That matches the exact current source, so that thread can be resolved.

Four threads remain for explicit disposition:

  • R4-3: whole-field replacement versus retaining diagnostic text is still a maintainer policy decision. The old auto-close claim is no longer true: the PR uses Refs #11198, GitHub returns no closing-issue references, and the issue's implementation update now records whole-field replacement without approving the policy.
  • R4-2: in-place enqueue mutation and what the tests observe need an explicit contract. No affected in-tree caller was demonstrated; no API rewrite is being made here.
  • R4-1: non-command hook failures lose raw diagnostic causes without an equivalent structured discriminator. An enum through the hook/telemetry layers is a separate design decision; restoring raw text is not the fix.
  • R5-2: the hook test's missing raw-value assertion is a valid, bounded regression-test improvement, not proof that the current producer leaks. It remains paused at the substantive-round limit; accepting it does not require a generalized telemetry redesign.

Recommendation: explicitly accept or reject whole-field masking first. I favor keeping raw error text out of this default-on channel, with structured diagnostics designed separately. No broader privacy guarantee is claimed for unknown keys, snapshots, stack, or other telemetry sinks. No additional review run is requested for this comment-only pass.

`redactEventErrorText` already replaces every string under `error_message` /
`error_excerpt` / top-level `message` with a fixed marker at the enqueue
boundary, so `logHookCallEvent`'s `getTelemetryLogPromptsEnabled()` gate on
`properties['error']` could only ever emit the constant `'***REDACTED***'` —
a consent flag that no longer gates any content. Hook failure is already
signalled by `success` / `exit_code`, so the raw error text is dropped
fail-closed instead, and the now-dead `'error'` entry is removed from
`ERROR_TEXT_PROPERTY_KEYS`.

The failed-hook test now drives a config with telemetry log prompts enabled
and asserts `properties` carries no `error` key, which goes red when the
gated assignment is restored.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtycy85205
@github-actions github-actions Bot removed the review/self-reported The linked issue was opened by the PR author (self-reported) label Sep 12, 2026
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Removed the automatic-closing keyword from the PR and made the current whole-field replacement policy and pending maintainer decision explicit in its description. This corrects the stale completion claim; it does not decide the privacy-versus-diagnostics tradeoff. Leaving the policy thread open for that decision.

中文说明

已移除 PR 的自动关闭关键字,并明确当前实现是整字段替换、仍需维护者决定隐私与诊断信息的取舍。此次仅纠正完成声明,不代替策略决策,因此保留本线程。

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Files reviewed

  • packages/core/src/telemetry/qwen-logger/qwen-logger.ts
  • packages/core/src/telemetry/qwen-logger/qwen-logger.test.ts

Findings

No blockers found.

Clean areas

  • Single choke point: redactEventErrorText is called at the top of enqueueLogEvent before the event enters the deque. All callers route through enqueueLogEvent, so no error text can bypass redaction.
  • Fail-closed hook approach: Instead of redacting properties['error'], the PR removes the hook error forwarding code entirely. Hook failures still signal via success: 0 / exit_code. Most conservative option — eliminates the leakage class rather than sanitizing it.
  • Redaction coverage: ERROR_TEXT_PROPERTY_KEYS = ['error_message', 'error_excerpt'] plus top-level message on exception/resource events covers all error-text carriers.
  • Type safety: (event as RumExceptionEvent).message cast is TypeScript-only narrowing; typeof message === 'string' guard prevents mutation on events without the field.
  • Test correctness: Uses toEqual for exact property-shape matching, correctly verifies redaction while preserving error_type. Hook test correctly asserts not.toHaveProperty('error').

Needs human review

  • Diagnostic trade-off: Hook error text is now completely absent from telemetry. Confirm that success: 0 + exit_code provides sufficient diagnostic signal.
  • Stale CHANGES_REQUESTED: The standing review references regex patterns that no longer exist at HEAD. Should be dismissed if the current approach is accepted, otherwise mergeStateStatus remains BLOCKED.

Reviewed with AI assistance.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE

核对基线:head ea640b45c2fde6767ebbff99712dac8b48461583。

历史阻塞问题:已解除

本 PR 历史上有三次 CHANGES_REQUESTED,最后一次是 2026-09-12T02:54:50Z 针对 41714d9f;当前 head 之后最新的一次 review(2026-09-12T14:57:47Z,针对 ea640b45)已降级为 COMMENTED,不再要求变更。43 条线程里 4 条未解决(R4-1、R4-2、R4-3、R5-2),逐条读过,全部是 [Suggestion] 级,没有 Critical、安全、数据损坏或回归级别的历史阻塞问题。按本渠道策略 Suggestion 不作为合入门禁。

本轮独立扫描:未发现 Critical

这是一个隐私收口改动,我按「是否存在绕过 redaction 的出口」和「是否存在未被覆盖的错误文本载体」两条主线核对:

  1. redaction 确实是唯一收口点。 出站队列 this.events 的写入只有两处:enqueueLogEvent 内的 this.events.push(event)(:195),以及重试回填的 this.events.unshift(eventsToRequeue[i])(:1175)。前者的第一行就是 this.redactEventErrorText(event)(:187);后者回填的是此前已经入过队、因而已经被改写过的事件,不会把原文重新带回来。发送侧 this.events.toArray()(:304、:377)只从队列读取,没有旁路构造 payload 的路径。因此所有出站事件都经过 redaction。
  2. 错误文本的载体被覆盖完整。 该 sink 里携带错误文本的字段只有三个:properties.error_message(:574、:703、:880-881、:908)、properties.error_excerpt,以及 exception 事件顶层的 message(:696、:716)。ERROR_TEXT_PROPERTY_KEYS 覆盖了前两个,redactEventErrorText 的后半段单独处理了 message(:218-221),且都做了 typeof === 'string' 判定,不会把非字符串值改坏。我另外按 error|stack|stack_trace|details|excerpt|reason|message 扫过整个文件的字段位,没有发现第四个承载原文的键。
  3. hook 路径的处理是收紧而非放松。 删掉的 if (event.error && this.config?.getTelemetryLogPromptsEnabled()) properties['error'] = event.error; 原本会在开启 logPrompts 时把 hook 的原始错误文本放进 properties.error——而这个键不在 ERROR_TEXT_PROPERTY_KEYS 里。直接删除该赋值后,这条路径不再产生任何错误原文,方向是更严格,不构成泄漏。
  4. 过度改写只发生在一处且是安全方向。 :729 的 message: \Content retry failed after ${event.total_attempts} attempts`是只含数字的构造串,也会被统一改写成REDACTED`。这是损失一点诊断信号,不是正确性问题,且失败方向是「少发数据」,可以接受。

CI:当前 head 上 Lint & Static、Test (ubuntu-latest, Node 22.x)、Integration Tests (no-AK, No Sandbox)、review-pr 全部 pass,没有由本 PR 引入的失败。

其余意见

未解决的 4 条 Suggestion 里有两条值得后续处理,但都不阻塞本次合入:R4-2 指出 redactEventErrorText 是就地改写传入的事件对象(生产路径上事件由各 create*Event 帮助函数在 enqueue 前逐次新建,因此不影响调用方状态,但测试若在 enqueue 后再观察同一对象就会看到被改写后的值,容易写出恒真断言);R4-3 指出本 head 采用的 redaction 策略与关联 issue 上记录的相反,而 Fixes #11198 会自动关闭该 issue,建议在 issue 或 PR 里补一句说明以免留下矛盾记录。

结论:历史阻塞问题已解除,本轮未发现可证明的 Critical,提交 APPROVE。

@yiliang114
yiliang114 dismissed a stale review September 12, 2026 16:35

fixed

@yiliang114
yiliang114 added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 85509c7 Sep 12, 2026
166 of 167 checks passed
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Posted as a PR comment rather than a review: this head already carries a pending review by the author (11 drafted replies), and GitHub rejects a second pending review. Nothing was deleted.

Reviewed at ea640b45c2 (2 files, +51/-47) against e40bf35eb9.

Verdict: no blocking defect at this head. The approach is right and strictly narrower than main: the enqueue boundary is the one place error text can leave the process, and a whole-field replacement cannot leak a substring the way a partial-text policy can. The regex-masking implementation the earlier rounds reviewed is not present in this revision at all.

Two notes that are real but non-blocking. Both are already carried by the standing bot threads (R4-4 / R3-8 / R5-1), so I am not re-filing them as new inline comments:

  1. The redaction is an allowlist, so it fails open for unlisted keys (qwen-logger.ts:106, :207-221). error_message / error_excerpt plus the top-level message are covered, and the hook error property is dropped at its producer instead of being added to the list. No producer writes properties.error today, so the gap is latent rather than live -- but the "single enqueue boundary" claim is true only for the enumerated keys, and any later producer that adds another error-bearing key bypasses it silently. Note also that error_excerpt has no producer anywhere in the tree (only the constant references it), so the description's field inventory does not match this head.
  2. Over-redaction of a benign message (qwen-logger.ts:218-221, producer at :729). logContentRetryFailureEvent sets the top-level message to the fixed, non-sensitive template Content retry failed after N attempts; the blanket top-level redaction replaces it with ***REDACTED***, so every error/content_retry_failure event loses its only human-readable description. Redacting by provenance -- have the two producers that actually carry error text pass the marker -- keeps the fixed exception templates intact.

Two things I checked and found clean, so they are not findings:

  • The as RumExceptionEvent cast at :218 is not masking a missing field: RumResourceEvent does declare message, so the logApiErrorEvent resource path is covered at runtime.
  • The in-place mutation of the caller's object is not reachable: every real call site builds the event through createRumEvent and drops the reference afterwards.

On the standing CHANGES_REQUESTED reviews. 7df466e0, 566d6a74 and 41714d9f are all pinned behind this head, and the code they reviewed -- the AUTHORIZATION_PATTERN / ENV_SECRET_PATTERN / SECRET_FLAG_VALUE family, including the round-3 ledger Critical about the 10-character flag floor -- no longer exists in this revision. The last review taken at this head (round 5) carries Suggestion-level findings only. Dismissing those stale reviews is legitimate; neither note above needs to block merge.

@qwen-code-dev-bot qwen-code-dev-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES

已核对 head ea640b45c2fde6767ebbff99712dac8b48461583(vs merge-base 20ecdaf6b2)。required CI 全绿。方向我是赞成的:把遮罩从生产者侧移到 sink 入口(enqueueLogEvent → redactEventErrorText,qwen-logger.ts:184-221)比逐个生产者各自记得脱敏可靠,error_message / error_excerpt / exception message 三类字符串字段被整体替换为固定标记,按构造就不会漏。拦的是下面两条,都落在本改动自己身上。

1. 这个改动自己的隐私保证没有用例钉住(线程 R5-2,成立)

qwen-logger.test.ts:782-824 这条改名成 “without forwarding raw error text” 的用例,唯一的值级断言是 expect(callArgs.properties).not.toHaveProperty('error'),其余全是 expect.objectContaining(容忍新增键),而它自己的 fixture 仍然带着 stderr: 'error output' 与 error: 'Command failed'(第 10、11 位实参)。所以同样的原文换到 hook_error / failure_reason 任何别的键下,这条用例照绿、原文照发到 RUM。上面的注释还写着「no raw error property is forwarded to the sink」,即用例在声明一个它并不检查的保证;而 sink 的防护本身是按键名匹配的黑名单,对新键名不设防,这一点恰是需要值级断言的理由。

同文件 :762-779 已经示范了该写的形状:把事件序列化后对原文取 not.toContain。请把这条补上(对 fixture 里的两个原文串断言 not.toContain),否则本 PR 的核心契约只有键名缺席在守。

2. 上一轮的修法顺手让 4 类 hook 里 3 类的失败再无归因(线程 R4-1,成立)

本轮删掉了 properties['error'] = event.error(连同 ERROR_TEXT_PROPERTY_KEYS 里的 'error'),留下的注释理由是「失败已由 success / exit_code 表明」。这个前提只对 hook_type: 'command' 成立:HookExecutionResult.exitCode 是可选的(hooks/types.ts:1340),全仓非测试代码里只有 hookRunner.ts:1439 会写它(http / prompt / function 三类都不写),logHookCallEvent 只有 6 个属性,序列化时 undefined 键被丢弃,于是 HTTP hook 的 URL 校验失败、被中止、超时、传输错误四类在用量遥测里落成逐字节相同的记录,hook_type 之外没有任何区分因。

请给一个不含自由文本的归因位(例如固定的错误类别枚举,或对非命令类失败也落一个 sentinel),并保持整字段遮罩不回退;如果决定本轮就不补,请把这一点写进 PR 正文的取舍段并在 #11198 上记一笔,别让它随合并静默消失。

两条已核为过期或不阻塞的说明

  • 线程 R4-3 说本 head 的隐私策略与 #11198 上记录的方案相反、且 Fixes #11198 会在合并时自动关闭该 issue。策略相反这点确实成立(issue 上作者 2026-09-11T10:07 那条还在说 shape-based masking「让错误保持可调试」,现已改为整字段替换),但 自动关闭的前提在本 head 已不成立:PR 正文用的是 Refs #11198(:49、:102),并明确写了「this PR must not automatically close that issue before the decision is recorded」。不过该 issue 仍带 status/ready-for-human、category/security、scope/data-privacy 且核心遥测 owner 已两次把「遮罩策略由维护者定」升级上来 —— 请在合并前先由维护者在 #11198 记下裁决(整字段替换 vs 形状遮罩),别让这个 P1 隐私决策随合并默认生效。
  • 线程 R4-2(redactEventErrorText 就地改写调用方对象、createRumEvent 浅拷贝 properties,QwenLogger 又是指向公开的包 API)我复核为事实,但正如提出者所说,当前 45 个左右调用点都传新构造的字面量,所以今天不伤生产,只伤测试可观察性与易用性。建议顺手改成先浅拷贝再脱敏、入队那份拷贝,断言指向真正被序列化的对象(同文件 :280 有现成写法)。

qwen-code-dev-bot added a commit to bluefateludi/qwen-code that referenced this pull request Sep 12, 2026
Merge origin/main into codex/issue-11198-redact-rum-errors.

Main landed QwenLM#11649, which replaces the RUM enqueue boundary's `message`,
`error_message`, and `error_excerpt` with a fixed marker outright. This
branch masks only credential-bearing shapes in those same fields plus
`stack` and `properties.error`. Both sides edited the one choke point, so
the calls collided.

Keep both passes rather than picking a side: the blanket one runs first and
owns the three fields it replaces, then the targeted one covers what it
leaves raw. Neither side's coverage is lost, and the stricter base-branch
policy is not weakened. `redactErrorText` is a no-op on the marker, so the
order is not output-load-bearing and only avoids re-scanning text that is
about to be discarded.
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Post-merge review (request-changes after merge) acknowledged: the two substantive findings — the missing value-level redaction pin and the lost failure attribution for non-command hooks — are tracked in #11760. The strategy note was already recorded on #11198.

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.

5 participants