Skip to content

fix(sandbox): keep redacted session keys UTF-16 safe#102969

Merged
steipete merged 2 commits into
openclaw:mainfrom
mushuiyu886:fix/followup-102625
Jul 9, 2026
Merged

fix(sandbox): keep redacted session keys UTF-16 safe#102969
steipete merged 2 commits into
openclaw:mainfrom
mushuiyu886:fix/followup-102625

Conversation

@mushuiyu886

@mushuiyu886 mushuiyu886 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Keep sandbox blocked-tool session key redaction UTF-16 safe for both the displayed prefix and suffix.
  • Add a regression through formatSandboxToolPolicyBlockedMessage so the user-visible blocked-tool guidance path is covered.

What Problem This Solves

Sandbox blocked-tool guidance redacts long session keys as prefix…suffix. The old formatter used trimmed.slice(0, 6) and trimmed.slice(-6), so a session key with an emoji or other astral character at either redaction boundary could leave a lone UTF-16 surrogate in the displayed Session: line.

Reviewed origin/main still has the raw slices at src/agents/sandbox/runtime-status.ts:

origin/main=cd50d4983b2f0fdfffe550449c40b2024d2991be
113:  return `${sanitizeForSingleLineDisplay(trimmed.slice(0, 6))}…${sanitizeForSingleLineDisplay(trimmed.slice(-6))}`;

User Impact

  • Why it matters / User impact: Users reading sandbox tool-policy denial messages should see a compact, terminal-safe, valid Session: value. Unicode session keys should not render replacement characters or malformed half-surrogates in the guidance that tells the user which sandboxed session was blocked.

Origin / follow-up

Follows #102625.

  • Follows: fix(ui): keep event payload previews UTF-16 safe #102625 fixed the same UTF-16-safe truncation class in UI event payload display by moving raw slicing to a shared safe truncation helper.
  • Gap: Sandbox blocked-tool guidance still used raw code-unit slicing for the redacted session key prefix and suffix.
  • Consistency: This applies shared UTF-16-safe prefix/suffix truncation at the source formatter while preserving existing control-character escaping and command guidance.

Competition / linked PR analysis

  • Related open PR scan: checked open PRs for sandbox redacted session key UTF-16 in openclaw/openclaw before submission.
[]

No open same-point PR was found.

Merge risk

  • Risk labels considered: security-boundary, session-state, compatibility, automation, availability.
  • Risk explanation: The relevant surfaces are sandbox session-state display and security-boundary guidance. The patch does not change whether sandboxing applies, which tool policy blocks a tool, audit behavior, allow/deny matching, or the shell command target. It only changes how an already-selected session key is shortened for display.
  • Why acceptable: The diff is limited to the redaction helper and one exported blocked-message regression. Existing control-character escaping remains in place, and the full session key remains used for the shell-escaped explain command when safe.
  • Patch quality notes: Focused source change plus a regression that calls the exported message formatter with a real sandbox config. No skip/only directives, no type escapes, no fallback/default strategy, and no unrelated cleanup.
  • Maintainer-ready confidence: High: latest main still has the raw slices, no same-point open PR was found, the patch uses existing UTF-16-safe helpers, and the regression covers the user-visible blocked-tool message path.

Real behavior proof

  • Behavior or issue addressed: A sandboxed session key whose prefix or suffix redaction boundary lands inside an emoji should not surface a malformed half-surrogate in the redacted Session: line of blocked-tool guidance.
  • Canonical reachability path: user/config/input → schema/type/ingestion/normalization → runtime object → request/effect: user/session input (sessionKey) plus sandbox config (agents.defaults.sandbox.mode=all, tool deny policy) enters formatSandboxToolPolicyBlockedMessage, is resolved by resolveSandboxRuntimeStatus and resolveSandboxToolPolicyForAgent, classified by classifyToolAgainstSandboxToolPolicy, then renders the user-visible blocked-tool guidance effect containing Session: ....
  • Boundary crossed: The regression calls the exported formatSandboxToolPolicyBlockedMessage path with a real sandbox config and denied tool, then observes the returned multiline guidance exactly as a caller would display it.
  • Shared helper / provider constraint check: The fix uses existing shared UTF-16-safe helpers from @openclaw/normalization-core/utf16-slice: truncateUtf16Safe for the prefix and sliceUtf16Safe for the suffix. No external provider constraint is involved.
  • Real environment tested: Sanitized direct AWS Crabbox (provider=aws, lease cbx_e5d64f409c98, public network, no Tailscale, no IAM instance profile, no credential hydration), Node 24.15.0, pnpm 11.2.2, exact PR head a2ed154337cb729e2dc1b58ca1b0ba50cf8b844a.
  • Exact steps or command run after this patch:
pnpm test src/agents/sandbox/tool-policy.test.ts -- -t 'keeps redacted session keys UTF-16 safe'
pnpm test src/agents/sandbox/tool-policy.test.ts
  • Evidence after fix:
Focused boundary regression:
 Test Files  1 passed (1)
      Tests  3 passed | 10 skipped (13)

Full scoped file:
 Test Files  1 passed (1)
      Tests  13 passed (13)
Old-slice negative control:
 Test Files  1 failed (1)
      Tests  3 failed | 10 passed (13)
prefix: received abcde�…123456
suffix: received abcdef…�12345
both: received abcde�…�12345
  • Run artifacts: focused green, full scoped green, old-slice red, exact-head CI.
  • Exact-head CI result: completed successfully at a2ed154337cb729e2dc1b58ca1b0ba50cf8b844a with 44 passed jobs, 11 scoped skips, and no failures.
  • Observed result after fix: Prefix-only, suffix-only, and simultaneous boundary splits produce exact compact labels with no unpaired high or low surrogate and remain within the existing 13-code-unit redaction budget. The copy-paste explain command still contains the intact full Unicode session key; only the display label is redacted.
  • What was not tested: No browser or external service is involved in this path. The proof does not run an actual blocked tool invocation; it verifies the exported formatter that produces the blocked-tool guidance after policy classification.
  • Fix classification: Root cause fix

Review findings addressed

  • Expanded the original prefix-only regression to cover suffix-only and simultaneous prefix+suffix splits.
  • Asserted both kinds of unpaired surrogate, the existing 13-code-unit label budget, exact rendered labels, and preservation of the intact Unicode key in the --session explain command.
  • Fresh Codex autoreview at exact head: clean, no accepted/actionable findings (patch is correct, confidence 0.91).

Regression Test Plan

  • Target test file: src/agents/sandbox/tool-policy.test.ts.
  • Scenario locked in: Table cases place an emoji across the prefix boundary (abcde\u{1F600}middle123456), suffix boundary (abcdefmiddle\u{1F600}12345), and both boundaries (abcde\u{1F600}middle\u{1F600}12345) while a real sandbox config denies browser and forces formatSandboxToolPolicyBlockedMessage to render the user-visible guidance.
  • Why this is the smallest reliable guardrail: It reaches the exact user-visible message formatter through the exported sandbox policy path while keeping the assertion focused on the redacted session key boundary.
pnpm test src/agents/sandbox/tool-policy.test.ts -- -t 'keeps redacted session keys UTF-16 safe'
pnpm test src/agents/sandbox/tool-policy.test.ts

Risk / Compatibility

Existing redaction shape remains prefix…suffix, and existing control-character escaping is preserved. The only observable change is that redaction will not split a surrogate pair; the displayed prefix or suffix may be one UTF-16 code unit shorter when the boundary lands inside an astral character.

What was not changed

  • What did NOT change: Does not change sandbox mode resolution, agent/session resolution, tool allow/deny classification, audit behavior, shell escaping, unsafe control-character handling, command formatting, or full session-key use in safe explain commands.
  • Architecture / source-of-truth check: Source-of-truth boundary: redactSessionKey is the canonical formatter used by formatSandboxToolPolicyBlockedMessage before the blocked-tool guidance is returned. This patch fixes that source formatter rather than post-processing a terminal mirror, log copy, or test-only representation.

Root Cause

  • Root cause: Root cause is the source formatter redactSessionKey shortening the session key prefix and suffix with raw JavaScript slice. Because JavaScript string indices are UTF-16 code units, slicing at fixed display boundaries can cut between the high and low surrogate of an emoji or another astral code point.
  • Why this is root-cause fix: The patch replaces both raw code-unit slices at the redaction source with shared UTF-16-safe helpers. Every long session key displayed by sandbox blocked-tool guidance flows through redactSessionKey, so the invalid-surrogate source is removed before the message is assembled.

@mushuiyu886
mushuiyu886 requested a review from a team as a code owner July 9, 2026 15:33
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS labels Jul 9, 2026
@steipete steipete self-assigned this Jul 9, 2026
@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 9, 2026, 1:13 PM ET / 17:13 UTC.

Summary
The PR replaces raw sandbox session-key redaction slices with shared UTF-16-safe prefix/suffix helpers and adds table-driven formatter regression cases.

PR surface: Source +1, Tests +51. Total +52 across 2 files.

Reproducibility: yes. at source level: current main uses raw UTF-16 code-unit slices for the redacted sandbox Session: prefix and suffix, and the PR's boundary examples would split an emoji without the safe slicer. I did not run a live blocked-tool invocation in this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: canonical
Canonical: #102969
Summary: This PR is the current canonical same-point fix for sandbox blocked-tool Session: redaction; related PRs address the same UTF-16 truncation class on other display surfaces.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🦞 diamond lobster
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Add redacted terminal output, logs, or a recording from an actual sandbox blocked-tool denial showing the fixed Session: line and guidance.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body adds Crabbox/Vitest formatter output, a negative control, and CI success, but still does not show a real blocked-tool sandbox denial after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] The remaining merge risk is proof sufficiency: the current evidence is strong formatter-level regression proof, but it does not show the actual sandbox blocked-tool denial output after the fix.

Maintainer options:

  1. Decide the mitigation before merge
    Keep this PR open until a linked canonical PR proves it covers this PR's unique work, or a maintainer confirms closure.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] No ClawSweeper code repair is indicated; the remaining action is contributor real behavior proof or an explicit maintainer proof override.

Maintainer decision needed

  • Question: Should this PR be allowed to merge on formatter-level Crabbox regression proof, or should it wait for an actual blocked-tool denial transcript/output?
  • Rationale: The external-PR proof policy normally requires after-fix real behavior proof, while the assigned reviewer has called the patch land-ready without a visible proof: override label.
  • Likely owner: steipete — He is the current assignee and already posted the land-ready review on the proof question.
  • Options:
    • Require actual denial proof (recommended): Wait for redacted terminal output, logs, or a recording from a real sandbox blocked-tool denial showing the fixed Session: line and guidance.
    • Accept proof override: A maintainer may explicitly mark the formatter-level proof as sufficient for this non-browser, non-network text path and proceed with normal landing gates.

Security
Cleared: The diff touches sandbox guidance text but only changes redacted display slicing and tests; it does not alter sandbox enforcement, permissions, secrets, dependencies, workflows, or package execution.

Review details

Best possible solution:

Keep this PR open until a linked canonical PR proves it covers this PR's unique work, or a maintainer confirms closure.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: current main uses raw UTF-16 code-unit slices for the redacted sandbox Session: prefix and suffix, and the PR's boundary examples would split an emoji without the safe slicer. I did not run a live blocked-tool invocation in this read-only review.

Is this the best way to solve the issue?

Yes for the implementation: fixing redactSessionKey with the existing UTF-16-safe helpers is the narrow owner-boundary fix and avoids duplicating helper logic. The only unresolved part is proof sufficiency for the contributor gate.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against f1e7081b4d36.

Label changes

Label justifications:

  • P2: This is a focused sandbox guidance display bug with limited blast radius and no evidence of sandbox enforcement or security-boundary behavior changing.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦞 diamond lobster.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body adds Crabbox/Vitest formatter output, a negative control, and CI success, but still does not show a real blocked-tool sandbox denial after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +1, Tests +51. Total +52 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 1 +1
Tests 1 51 0 +51
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 53 1 +52

What I checked:

  • PR close coverage proof: PR close coverage proof kept this PR open against refactor(sandbox): remove tool policy facade #54684: PR B is related provenance for the same formatter, but it is only partial overlap. The durable PR A report says the UTF-16 truncation bug remains on current main after PR B existed and merged, and the PR B evidence does not directly carry that exact fix.
  • linked superseding PR: refactor(sandbox): remove tool policy facade #54684 (refactor(sandbox): remove tool policy facade) is merged at 2026-03-25T21:03:24Z.
  • cluster evidence: the durable review links that PR in the work cluster or recommended risk path.
  • no human follow-up: live comments and timeline hydrated by apply contain no non-automation activity after the ClawSweeper review.

Likely related people:

  • ngutman: Authored and merged the sandbox tool-policy facade removal that made the current blocked-message formatter canonical and touched the same runtime status and test files. (role: introduced or carried forward behavior; confidence: high; commits: 501190d2e833; files: src/agents/sandbox/runtime-status.ts, src/agents/sandbox/tool-policy.ts, src/agents/sandbox/tool-policy.test.ts)
  • steipete: Assigned himself to this PR, posted the land-ready review, authored the PR's expanded boundary-test commit, and current local blame points recent sandbox formatter/helper lines to his recent main-history commit in this checkout. (role: recent area reviewer and adjacent contributor; confidence: medium; commits: a2ed154337cb, b8cdb93f6ffa; files: src/agents/sandbox/runtime-status.ts, src/agents/sandbox/tool-policy.test.ts, packages/normalization-core/src/utf16-slice.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (1 earlier review cycle)
  • reviewed 2026-07-09T17:06:21.511Z sha a2ed154 :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jul 9, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Land-ready review at exact head a2ed154337cb729e2dc1b58ca1b0ba50cf8b844a.

What changed:

  • redactSessionKey now uses the existing normalization-core UTF-16 helpers for both the six-code-unit prefix and suffix, preserving the existing compact prefix…suffix disclosure budget and control-character handling.
  • The exported blocked-message path now has table coverage for prefix-only, suffix-only, and simultaneous surrogate splits. Each case asserts the exact label, the 13-code-unit maximum, no unpaired high or low surrogate, and preservation of the intact Unicode session key in the shell-escaped explain command.

Review result:

  • Best-fix verdict: best fix. This repairs the canonical display formatter at the owner boundary with the shared dependency contract. Post-sanitizing a malformed raw slice cannot reconstruct the lost pair, and a new local code-point helper would duplicate the established normalization-core implementation.
  • Code read: src/agents/sandbox/runtime-status.ts, its barrel and production caller in src/agents/embedded-agent-helpers/errors.ts, sandbox policy/config resolvers, adjacent sandbox tests, escapeControlCharsVisible, CLI command formatting, and packages/normalization-core/src/utf16-slice.ts plus its tests/exports.
  • Sibling check: src/commands/sessions-table.ts is a distinct unredacted fixed-width table surface and is outside this sandbox blocked-guidance fix.
  • Provenance: the raw redaction slices were carried onto main by refactor(sandbox): remove tool policy facade #54684 on 2026-03-25; @ngutman authored and merged that refactor. This PR is by @mushuiyu886.

Proof:

Merge intentionally held for the native landing-gate dependency requested by the maintainer; no merge was attempted in this pass.

@steipete
steipete merged commit b714fdb into openclaw:main Jul 9, 2026
111 of 113 checks passed
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 10, 2026
* fix(sandbox): keep redacted session keys UTF-16 safe

* test(sandbox): cover UTF-16 redaction boundaries

---------

Co-authored-by: Peter Steinberger <[email protected]>
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jul 14, 2026
…ster regression

Adds the mushuiyu886 openclaw#102969/openclaw#102949 proof shape to the existing regression:

- LABEL_TRUNCATION_MODE=baseline swaps the SDK truncateUtf16Safe with raw
  String.prototype.slice via vi.mock, so the same test cases run as
  control-red on the buggy code. Three of the four boundary cases fail
  with 'lone high surrogate detected' (one passes: the no-cut baseline).
- LABEL_PROOF_DUMP=1 writes a [proof] line per case with the actual
  length=, hex_tail= of the parsed display_name= value plus the
  isLoneHighSurrogate verdict. Crabbox can capture the line and the PR
  body can paste it next to the run URL.
- vi.hoisted() lifts the env-var reads so the vi.mock factory (which is
  hoisted to the top of the file) can reference them safely.

No production code change.  No new SDK surface.  No touched files
outside extensions/discord/src/voice/.
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jul 14, 2026
…gressContext

P1 follow-up: the regression test now drives the production
resolveDiscordVoiceIngressContext end-to-end instead of mocking it.

Before: vi.mock("./ingress.js") returned a stub
{ senderIsOwner: false, speakerLabel: "tester" } with no
extraSystemPrompt. The test still exercised the real
appendDiscordVoiceParticipantContext, but the upstream auth + speaker
identity layer was stubbed.

After: the upstream resolveDiscordVoiceIngressContext is the real
production function. The test passes:
- a real DiscordVoiceSpeakerContextResolver instance with a stub
  client whose fetchMember / fetchUser return emoji-laden display data
- cfg = {} and discordConfig = { groupPolicy: "open" } so
  authorizeDiscordVoiceIngress returns { ok: true, channelConfig: null }
  via the owner-allow-all short-circuit and the default-open group
  policy path
- ownerAllowAll: true so the resolveCommandAuthorizedFromAuthorizers
  authorizer is { configured: true, allowed: true }

What now runs for real (not mocked):
- DiscordVoiceSpeakerContextResolver (class, not a stub): exercises
  resolveIdentity + cache + resolveIsOwner
- authorizeDiscordVoiceIngress: real production auth check
- buildDiscordGroupSystemPrompt: real production prompt builder
- appendDiscordVoiceParticipantContext: real production roster builder

What stays mocked:
- the SDK truncateUtf16Safe (only via LABEL_TRUNCATION_MODE=baseline
  vi.mock, which is the test-only control-red affordance)
- the gateway plugin client (the only way to inject listVoiceChannelStates
  state without a real Discord connection)

P2 follow-up: also add the ephemeral worktree control-red shape (the
mushuiyu886 openclaw#102949 / openclaw#102969 gold standard). The test file itself is
unchanged for P2; the proof is captured by running the test in an
ephemeral worktree with the production code reverted to the buggy raw
slice. See 4.5 of the PR body for the full transcript.

Local verification:
- oxlint --threads=1 on the test file: clean (exit 0)
- vitest in fix mode (default): 4 / 4 pass, hex_tail all clean
- vitest in baseline mode (LABEL_TRUNCATION_MODE=baseline): 3 / 4 fail
  with lone high surrogate detected at the boundary
- vitest in ephemeral worktree with participant-context.ts reverted to
  the original slice(0, 100): 3 / 4 fail with the same lone high
  surrogate; the no-cut baseline still passes
- vitest with LABEL_PROOF_DUMP=1 in either mode: emits the [proof]
  line with length= / hex_tail= / lone_surrogate= / sample= per case

Co-Authored-By: Claude <[email protected]>
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jul 14, 2026
…ster regression

Adds the mushuiyu886 openclaw#102969/openclaw#102949 proof shape to the existing regression:

- LABEL_TRUNCATION_MODE=baseline swaps the SDK truncateUtf16Safe with raw
  String.prototype.slice via vi.mock, so the same test cases run as
  control-red on the buggy code. Three of the four boundary cases fail
  with 'lone high surrogate detected' (one passes: the no-cut baseline).
- LABEL_PROOF_DUMP=1 writes a [proof] line per case with the actual
  length=, hex_tail= of the parsed display_name= value plus the
  isLoneHighSurrogate verdict. Crabbox can capture the line and the PR
  body can paste it next to the run URL.
- vi.hoisted() lifts the env-var reads so the vi.mock factory (which is
  hoisted to the top of the file) can reference them safely.

No production code change.  No new SDK surface.  No touched files
outside extensions/discord/src/voice/.
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jul 14, 2026
…gressContext

P1 follow-up: the regression test now drives the production
resolveDiscordVoiceIngressContext end-to-end instead of mocking it.

Before: vi.mock("./ingress.js") returned a stub
{ senderIsOwner: false, speakerLabel: "tester" } with no
extraSystemPrompt. The test still exercised the real
appendDiscordVoiceParticipantContext, but the upstream auth + speaker
identity layer was stubbed.

After: the upstream resolveDiscordVoiceIngressContext is the real
production function. The test passes:
- a real DiscordVoiceSpeakerContextResolver instance with a stub
  client whose fetchMember / fetchUser return emoji-laden display data
- cfg = {} and discordConfig = { groupPolicy: "open" } so
  authorizeDiscordVoiceIngress returns { ok: true, channelConfig: null }
  via the owner-allow-all short-circuit and the default-open group
  policy path
- ownerAllowAll: true so the resolveCommandAuthorizedFromAuthorizers
  authorizer is { configured: true, allowed: true }

What now runs for real (not mocked):
- DiscordVoiceSpeakerContextResolver (class, not a stub): exercises
  resolveIdentity + cache + resolveIsOwner
- authorizeDiscordVoiceIngress: real production auth check
- buildDiscordGroupSystemPrompt: real production prompt builder
- appendDiscordVoiceParticipantContext: real production roster builder

What stays mocked:
- the SDK truncateUtf16Safe (only via LABEL_TRUNCATION_MODE=baseline
  vi.mock, which is the test-only control-red affordance)
- the gateway plugin client (the only way to inject listVoiceChannelStates
  state without a real Discord connection)

P2 follow-up: also add the ephemeral worktree control-red shape (the
mushuiyu886 openclaw#102949 / openclaw#102969 gold standard). The test file itself is
unchanged for P2; the proof is captured by running the test in an
ephemeral worktree with the production code reverted to the buggy raw
slice. See 4.5 of the PR body for the full transcript.

Local verification:
- oxlint --threads=1 on the test file: clean (exit 0)
- vitest in fix mode (default): 4 / 4 pass, hex_tail all clean
- vitest in baseline mode (LABEL_TRUNCATION_MODE=baseline): 3 / 4 fail
  with lone high surrogate detected at the boundary
- vitest in ephemeral worktree with participant-context.ts reverted to
  the original slice(0, 100): 3 / 4 fail with the same lone high
  surrogate; the no-cut baseline still passes
- vitest with LABEL_PROOF_DUMP=1 in either mode: emits the [proof]
  line with length= / hex_tail= / lone_surrogate= / sample= per case

Co-Authored-By: Claude <[email protected]>
wangmiao0668000666 added a commit to wangmiao0668000666/openclaw that referenced this pull request Jul 14, 2026
Round 2 follow-up: slim the regression test and PR body to match the
sibling UTF-16-safe cohort (openclaw#106328, openclaw#102949, openclaw#102969) template.

- Test: 304 -> 127 lines (-58%)
  - Removed LABEL_PROOF_DUMP / LABEL_RUN_LOG env vars + dumpDisplayName helper
  - Extracted renderRosterPrompt(nick) + parseDisplayName(prompt) helpers
  - Trimmed 47-line test-file header to 9 lines
  - Kept LABEL_TRUNCATION_MODE=baseline vi.mock for in-process control-red
  - All 4 emoji-boundary cases preserved
- Body: ~30KB -> 10.5KB (-64%)
  - Dropped Round 1/2/3 rebase history
  - Dropped hex code-unit dump
  - Dropped TODO Crabbox run placeholders
  - Restructured to 4-section template
- Production code unchanged: same 1-line + import + named constant patch.
  Same fix/baseline behavior (4/4 pass in fix mode, 3 fail in baseline).

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants