Skip to content

fix(whatsapp): make in-sandbox pairing QR scannable - #6396

Merged
cv merged 9 commits into
mainfrom
fix/whatsapp-pairing-qr-scannable
Jul 7, 2026
Merged

fix(whatsapp): make in-sandbox pairing QR scannable#6396
cv merged 9 commits into
mainfrom
fix/whatsapp-pairing-qr-scannable

Conversation

@laitingsheng

@laitingsheng laitingsheng commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

The bundled OpenClaw/WhatsApp login path renders openclaw channels login --channel whatsapp through renderQrTerminal(qr) with no { small } option. The previous compacting path made the QR fit in the terminal, but the real no-options login render still used qrcode's built-in compact terminal output with too little quiet zone for reliable scanning.

This PR installs a WhatsApp runtime preload that renders terminal QR output from qrcode.create(...).modules with a four-module quiet zone on all edges, and also hash-gates the pinned OpenClaw 2026.6.10 explicit { small: true } compact renderer rewrite. The preload does not print a credential-derived data:image/png fallback URL.

Related Issue

Fixes #6386

Changes

  • Move the WhatsApp QR fix into src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts and wire it through the manifest runtime preload path.
  • Render the real no-options WhatsApp login QR path with NemoClaw's four-module compact terminal renderer instead of qrcode's built-in small terminal renderer.
  • Keep the explicit OpenClaw { small: true } compact renderer source rewrite for the pinned reviewed renderer, including all four quiet-zone edges.
  • Emit non-secret warnings when the ESM source-loader registration, integrity check, or preimage match fails instead of silently skipping the source rewrite.
  • Keep non-terminal QR outputs unchanged and do not print raw data:image/png fallback data to terminal/log output.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: no new command, flag, or config surface; the change restores the already-documented in-sandbox WhatsApp pairing flow to a scannable terminal QR.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: awaiting maintainer review (messaging pairing flow + runtime preload).
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Verification

  • PR description includes the DCO sign-off declaration and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run check:diff passed when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification:
    • ./node_modules/.bin/vitest run --project cli src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts -> 23/23 passed
    • ./node_modules/.bin/vitest run --project integration test/whatsapp-qr-compact.test.ts -> 23/23 passed
    • NEMOCLAW_RUN_LIVE_E2E=1 ./node_modules/.bin/vitest run --project e2e-live test/e2e/live/whatsapp-qr-compact.test.ts --silent=false --reporter=default -> 1/1 passed
    • npm run test-conditionals:scan -- --top 25 -> passed
    • npm run typecheck:cli -> passed
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Tinson Lai tinsonl@nvidia.com

The bundled OpenClaw renders the WhatsApp pairing QR with a 1-module
quiet zone, below the 4 modules QR readers need to lock on, so the
terminal code cannot be scanned even from a screenshot. Patch the
compiled compact renderer to use a 4-module quiet zone and emit a
data:image/png fallback so pairing works regardless of terminal.

Fixes #6386

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@github-code-quality

github-code-quality Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in the fix/whatsapp-pairing... branch is 96%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main fix/whatsapp-pairing... a03d53b +/-
nemoclaw/src/se...cret-scanner.ts 100%
nemoclaw/src/commands/slash.ts 100%
nemoclaw/src/li...bprocess-env.ts 100%
nemoclaw/src/bl...eprint/state.ts 98%
nemoclaw/src/onboard/config.ts 98%
nemoclaw/src/bl...int/snapshot.ts 97%
nemoclaw/src/bl...print/runner.ts 95%
nemoclaw/src/co...ration-state.ts 94%
nemoclaw/src/bl...ate-networks.ts 94%
nemoclaw/src/index.ts 94%

TypeScript / code-coverage/cli

The overall coverage in the fix/whatsapp-pairing... branch is 75%. Coverage data for the main branch is not yet available.

Show a code coverage summary of the most covered files.
File main fix/whatsapp-pairing... a03d53b +/-
src/lib/shields...nsition-lock.ts 85%
src/lib/onboard/preflight.ts 83%
src/lib/actions...all/run-plan.ts 81%
src/lib/state/o...oard-session.ts 80%
src/lib/actions...licy-channel.ts 79%
src/lib/state/sandbox.ts 75%
src/lib/onboard...er-gpu-patch.ts 69%
src/lib/policy/index.ts 66%
src/lib/shields/index.ts 61%
src/lib/onboard.ts 28%

Updated July 07, 2026 17:35 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds runtime detection and patching for the OpenClaw WhatsApp QR terminal renderer to widen the compact quiet-zone margin and inject a data:image/png fallback for unscannable QR codes, installs the patch via an ESM Module.register loader hook, and extends unit and integration test coverage for the new behavior.

Changes

OpenClaw QR renderer runtime patch

Layer / File(s) Summary
OpenClaw renderer detection and patch logic
src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts, src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts
Adds isOpenClawQrTerminalRendererSource and patchOpenClawQrTerminalRendererSource to detect the OpenClaw renderer module and rewrite COMPACT_MARGIN_MODULES plus the opts.small render path with an async PNG data-URL fallback; unit tests verify detection, migration markers, and idempotency.
ESM loader installation wiring
src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts
Adds createOpenClawQrTerminalLoaderSource and installOpenClawQrTerminalSourceLoader, exports the new helpers, registers a base64 data:text/javascript ESM loader via Module.register guarded by a one-time install flag, and invokes it from the main hook setup.
Preload integration test harness
test/whatsapp-qr-compact.test.ts
Extends the fake qrcode module with create()/toDataURL() call tracking, updates the fake openclaw-qr-terminal.mjs preload to route compact vs. terminal rendering, adds a probe script exercising renderQrTerminal({ small: true }), and adds an assertion comparing baseline vs. patched compact output and fallback invocation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HookInstaller
  participant Module
  participant ESMLoader
  participant OpenClawRenderer

  HookInstaller->>Module: installOpenClawQrTerminalSourceLoader(Module)
  Module->>ESMLoader: register(base64 data:text/javascript loader URL)
  ESMLoader->>OpenClawRenderer: load() intercepts module source
  ESMLoader->>ESMLoader: isOpenClawQrTerminalRendererSource(source)
  ESMLoader->>OpenClawRenderer: return patched source (compact margin + PNG fallback)
  OpenClawRenderer->>OpenClawRenderer: renderQrTerminal(payload, opts.small)
  OpenClawRenderer-->>HookInstaller: compact QR plus data:image/png fallback message
Loading

Related issues: #6386

Suggested labels: bug, whatsapp, testing

Suggested reviewers:

🐰 A rabbit taps the QR code twice,
margins wide, no need for advice,
a data-URL hops in as backup,
so scanners no longer say "shut up",
hop, patch, and scan — WhatsApp's device!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #6386 by widening the QR quiet zone and adding a scan-fallback path, matching the issue’s scannability goal.
Out of Scope Changes check ✅ Passed The added runtime patching and tests are directly related to the WhatsApp QR fix, with no clear unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: making the WhatsApp pairing QR scannable in the sandbox.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/whatsapp-pairing-qr-scannable

Comment @coderabbitai help to get the list of available commands.

@laitingsheng laitingsheng added integration: whatsapp WhatsApp integration or channel behavior area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression NV QA Bugs found by the NVIDIA QA Team labels Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor (Nemotron Ultra) — Changes requested

Merge posture: Do not merge yet
Primary next action: Fix PRA-3: @ts-nocheck disables all TypeScript checking for security-critical sandbox preload; then add or justify PRA-T1.
Open items: 3 required · 16 warnings · 3 suggestions · 8 test follow-ups
Since last review: 1 prior item resolved · 12 still apply · 3 new items found

Action checklist

  • PRA-3 Fix: @ts-nocheck disables all TypeScript checking for security-critical sandbox preload in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1
  • PRA-4 Fix: Unit test runs TypeScript source via --require without TypeScript loader in test/whatsapp-qr-compact.test.ts:338
  • PRA-5 Fix: Module.register failure silently disables critical ESM patch with only warning in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:387
  • PRA-1 Resolve or justify: Source-of-truth review needed: patchQrcode render error catch (line 299)
  • PRA-2 Resolve or justify: Source-of-truth review needed: installOpenClawQrTerminalSourceLoader Module.register catch (line 395)
  • PRA-6 Resolve or justify: Fragile string-based renderer detection via indexOf checks in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:65
  • PRA-7 Resolve or justify: Hardcoded SHA256 hash not build-verified against Dockerfile OPENCLAW_VERSION in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:73
  • PRA-8 Resolve or justify: Function serialization via .toString() in template literal is Node-version dependent in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:215
  • PRA-9 Resolve or justify: Module.register(data: URL) pattern unconventional vs Slack's Module.registerHooks in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:394
  • PRA-10 Resolve or justify: Workaround removal condition lacks automated verification in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:40
  • PRA-11 Resolve or justify: PR claims data:image/png fallback but test expects dataImageFallback=false in test/e2e/live/whatsapp-qr-compact.test.ts:307
  • PRA-12 Resolve or justify: Missing test for Module.register failure diagnostic emission in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts:1
  • PRA-13 Resolve or justify: No actual QR decode validation — only proxy metrics in test/e2e/live/whatsapp-qr-compact.test.ts:1
  • PRA-T1 Add or justify test follow-up: Runtime validation
  • PRA-T2 Add or justify test follow-up: Runtime validation
  • PRA-T3 Add or justify test follow-up: Runtime validation
  • PRA-T4 Add or justify test follow-up: Runtime validation
  • PRA-T5 Add or justify test follow-up: Runtime validation
  • PRA-T6 Add or justify test follow-up: Unit test runs TypeScript source via --require without TypeScript loader
  • PRA-T7 Add or justify test follow-up: Missing test for Module.register failure diagnostic emission
  • PRA-T8 Add or justify test follow-up: Acceptance clause
  • PRA-17 In-scope improvement: Replace hasOwn helper with Object.hasOwn (stdlib) in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:20
  • PRA-18 In-scope improvement: Simplify markPatched with Symbol key in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:17
  • PRA-19 In-scope improvement: Replace for...in opts merge with Object.fromEntries filter (stdlib) in src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:128

Findings index

ID Severity Category Location Required action
PRA-1 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-2 Resolve/justify architecture Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
PRA-3 Required security src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1 Remove // @ts-nocheck and fix any type errors. If intentional for JS compatibility, use // @ts-expect-error on specific lines with justification comments.
PRA-4 Required tests test/whatsapp-qr-compact.test.ts:338 Update unit test to compile whatsapp-qr-compact.ts via tsconfig.runtime-preloads.json and require the .js output, matching the E2E test's compileProductionPreload pattern. Or configure vitest to transpile the preload for unit tests.
PRA-5 Required security src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:387 In sandbox contexts, make Module.register failure a hard failure (process.exit with clear message). At minimum, emit a structured diagnostic that includes the Node version and Module.register availability, and consider falling back to a CJS-based patch if feasible.
PRA-6 Resolve/justify correctness src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:65 Pin detection to specific OpenClaw version/hash. Compute expected source hash at build time (from pinned @openclaw/whatsapp version in Dockerfile) and verify at runtime before detection. Fall back to string detection only with warning. Or use AST-based detection for robustness.
PRA-7 Resolve/justify correctness src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:73 Add build-time script that extracts OPENCLAW_VERSION from Dockerfile.base, downloads the OpenClaw renderer source at that version, computes SHA256, and verifies against the constant. Fail build on mismatch. Or generate the constant at build time and inject it.
PRA-8 Resolve/justify correctness src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:215 Extract ESM loader to separate file (e.g., whatsapp-qr-compact-loader.ts) compiled alongside preload. Import and register via Module.register(fileUrl) instead of data: URL. This enables TypeScript checking on loader code and eliminates serialization fragility.
PRA-9 Resolve/justify architecture src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:394 Align with Slack's approach: extract loader to separate .ts file, compile it, register via Module.registerHooks or Module.register(fileUrl). Document why data: URL is required if it is.
PRA-10 Resolve/justify scope src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:40 Add build-time or CI check that measures OpenClaw's default QR output (without preload) and fails if it meets compact criteria (rows <= 40, quiet zone >= 4). This creates an automated 'remove me' signal.
PRA-11 Resolve/justify acceptance test/e2e/live/whatsapp-qr-compact.test.ts:307 Either implement the data:image/png fallback in the preload and update test to expect true, or remove fallback mention from PR description and update issue #6386 acceptance criteria to match actual implementation (terminal-only rendering with quiet zone).
PRA-12 Resolve/justify tests src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts:1 Add test using vi.spyOn(process.stderr, 'write') or capturing stderr to assert diagnostic emission on Module.register failure. Mock Module.register to throw or be undefined.
PRA-13 Resolve/justify acceptance test/e2e/live/whatsapp-qr-compact.test.ts:1 Add headless browser test that renders QR in terminal, captures screenshot, and validates with zxing/zbar decoder. Clarify fallback requirement: implement or remove from acceptance criteria.
PRA-14 Resolve/justify architecture src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:65 Add test that both CJS hook and ESM loader produce identical detection results on same renderer source. Consider separating detection from patching for clearer boundaries.
PRA-15 Resolve/justify security src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:376 Add Node version check: require Node >= 18.19.0 || >= 20.6.0 for ESM loader registration. If below, emit actionable warning with minimum version. Consider making this a hard requirement for sandbox environments.
PRA-16 Resolve/justify architecture src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1 Split into multiple files: whatsapp-qr-compact.ts (main entry, installs hooks), whatsapp-qr-compact-qrcode.ts (qrcode/qrcode-terminal patches), whatsapp-qr-compact-openclaw.ts (OpenClaw ES module detection/patch/loader), whatsapp-qr-compact-diagnostics.ts (warnWhatsappQrCompact). Keep exports for testability.
PRA-17 Improvement correctness src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:20 Replace hasOwn(mod, name) calls with Object.hasOwn(mod, name). Delete helper.
PRA-18 Improvement correctness src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:17 Use const PATCHED = Symbol('nemoclawCompactPatched'); then mod[PATCHED] = true; check via mod[PATCHED].
PRA-19 Improvement correctness src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:128 Replace with const createOpts = Object.fromEntries(Object.entries(opts).filter(([k]) => k !== 'type' && k !== 'small')); or similar.
PRA-20 Resolve/justify correctness src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:395 Add test for Module.register failure path (see PRA-11). Enforce minimum Node version in sandbox (see PRA-20) to make removal condition actionable.

🚨 Required before merge

Address these before merging unless a maintainer explicitly overrides the advisor with rationale.

PRA-3 Required — @ts-nocheck disables all TypeScript checking for security-critical sandbox preload

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1
  • Category: security
  • Problem: File-wide // @ts-nocheck directive prevents TypeScript from checking any code in this security-critical sandbox preload. Type errors in patch logic, hash verification, or Module.register handling would not be caught at compile time.
  • Impact: Security-critical preload runs with zero compile-time type safety. Errors in hash verification, source patching, or Module.register failure handling could reach production undetected.
  • Required action: Remove // @ts-nocheck and fix any type errors. If intentional for JS compatibility, use // @ts-expect-error on specific lines with justification comments.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read line 1 of whatsapp-qr-compact.ts — // @ts-nocheck is present. Run tsc -p tsconfig.runtime-preloads.json to see suppressed errors.
  • Missing regression test: Add build step that runs tsc --noEmit on the preload source and fails on any error; this prevents reintroduction of @ts-nocheck.
  • Done when: The required change is committed and verification passes: Read line 1 of whatsapp-qr-compact.ts — // @ts-nocheck is present. Run tsc -p tsconfig.runtime-preloads.json to see suppressed errors.
  • Evidence: // @ts-nocheck at line 1 of whatsapp-qr-compact.ts; tsconfig.runtime-preloads.json has strict: true but nocheck bypasses it

PRA-4 Required — Unit test runs TypeScript source via --require without TypeScript loader

  • Location: test/whatsapp-qr-compact.test.ts:338
  • Category: tests
  • Problem: Unit test spawns raw Node process with --require pointing to .ts source file (PRELOAD_SOURCE). Node cannot natively execute TypeScript; this test only passes if a global TypeScript loader (ts-node, vitest oxc) happens to be registered via NODE_OPTIONS, which is not guaranteed in the spawned process.
  • Impact: Test validity depends on ambient TypeScript loader. In clean CI environments without NODE_OPTIONS, the test would fail or silently test nothing. False confidence in preload behavior.
  • Required action: Update unit test to compile whatsapp-qr-compact.ts via tsconfig.runtime-preloads.json and require the .js output, matching the E2E test's compileProductionPreload pattern. Or configure vitest to transpile the preload for unit tests.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read test/whatsapp-qr-compact.ts runProbe function: spawnSync(process.execPath, ["--require", PRELOAD_SOURCE, ...]) where PRELOAD_SOURCE ends in .ts. No TypeScript loader is configured for the spawned process.
  • Missing regression test: Add CI check that runs the unit test in a clean Node environment (no NODE_OPTIONS) and verifies it passes — this proves the test doesn't rely on ambient TypeScript loaders.
  • Done when: The required change is committed and verification passes: Read test/whatsapp-qr-compact.ts runProbe function: spawnSync(process.execPath, ["--require", PRELOAD_SOURCE, ...]) where PRELOAD_SOURCE ends in .ts. No TypeScript loader is configured for the spawned process.
  • Evidence: PRELOAD_SOURCE points to .ts file; spawnSync uses raw process.execPath without -r ts-node/register or similar

PRA-5 Required — Module.register failure silently disables critical ESM patch with only warning

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:387
  • Category: security
  • Problem: Module.register(loaderUrl) try/catch at lines 391-397 now emits a warning via warnWhatsappQrCompact on failure, but the ESM loader registration is still silently skipped. The OpenClaw QR renderer ES module patch (quiet-zone widening on all four edges) is completely disabled with only a stderr warning. In sandbox environments where Module.register is unavailable (older Node, missing --experimental-loader, permissions), the critical quiet-zone patch never applies.
  • Impact: Operators never learn the security patch failed. OpenClaw QR renderer runs unpatched → oversized QR with no data:image/png fallback. WhatsApp pairing fails silently on Node versions without Module.register support or in restricted environments.
  • Required action: In sandbox contexts, make Module.register failure a hard failure (process.exit with clear message). At minimum, emit a structured diagnostic that includes the Node version and Module.register availability, and consider falling back to a CJS-based patch if feasible.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read installOpenClawQrTerminalSourceLoader function lines 376-398: try { Module.register(loaderUrl); } catch (_e) { warnWhatsappQrCompact(...); } — no hard failure, no fallback, ESM patch simply disabled.
  • Missing regression test: Add test asserting process.exit or structured error emission when Module.register is unavailable (e.g., by mocking Module.register = undefined). Verify CJS hook still works but ESM path is explicitly failed.
  • Done when: The required change is committed and verification passes: Read installOpenClawQrTerminalSourceLoader function lines 376-398: try { Module.register(loaderUrl); } catch (_e) { warnWhatsappQrCompact(...); } — no hard failure, no fallback, ESM patch simply disabled.
  • Evidence: Lines 391-397: try { Module.register(loaderUrl); } catch (_e) { warnWhatsappQrCompact("OpenClaw QR renderer source loader registration failed; explicit compact quiet-zone rewrite skipped"); }
Review findings by urgency: 3 required fixes, 16 items to resolve/justify, 3 in-scope improvements

⚠️ Resolve or justify before merge

Investigate these in the current review; either fix them, explain why they are not applicable, or document the accepted risk.

PRA-1 Resolve/justify — Source-of-truth review needed: patchQrcode render error catch (line 299)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Add test with mod.create returning malformed modules; verify callback receives error
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: } catch (err) { cb(err); return undefined; } in callback path

PRA-2 Resolve/justify — Source-of-truth review needed: installOpenClawQrTerminalSourceLoader Module.register catch (line 395)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as missing.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: MISSING — Add test mocking Module.register to throw/undefined; verify warning emitted and behavior documented
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: try { Module.register(loaderUrl); } catch (_e) { warnWhatsappQrCompact(...); } — no test, no version gate

PRA-6 Resolve/justify — Fragile string-based renderer detection via indexOf checks

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:65
  • Category: correctness
  • Problem: isOpenClawQrTerminalRendererSource uses string indexOf checks for function names and patterns (renderCompactTerminalQr, COMPACT_MARGIN_MODULES, async function renderQrTerminal, opts.small === true). This is fragile: minification, formatting changes, or upstream refactors will cause silent detection failure and the patch will be skipped.
  • Impact: OpenClaw renderer updates (even whitespace/formatting) cause silent patch skip. No automated detection of drift — only manual investigation when QR becomes unscannable.
  • Recommended action: Pin detection to specific OpenClaw version/hash. Compute expected source hash at build time (from pinned @openclaw/whatsapp version in Dockerfile) and verify at runtime before detection. Fall back to string detection only with warning. Or use AST-based detection for robustness.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read isOpenClawQrTerminalRendererSource at lines 65-72 — four indexOf checks on source strings.
  • Missing regression test: Add test that varies whitespace, formatting, and minification of the renderer source and verifies detection behavior (should either detect reliably or fail closed with warning).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read isOpenClawQrTerminalRendererSource at lines 65-72 — four indexOf checks on source strings.
  • Evidence: Function returns true only if all four indexOf checks pass: renderCompactTerminalQr, COMPACT_MARGIN_MODULES, async function renderQrTerminal, opts.small === true

PRA-7 Resolve/justify — Hardcoded SHA256 hash not build-verified against Dockerfile OPENCLAW_VERSION

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:73
  • Category: correctness
  • Problem: REVIEWED_OPENCLAW_QR_TERMINAL_RENDERER_SHA256 is hardcoded constant. No build-time verification that this hash matches the actual OpenClaw 2026.6.10 renderer source bundled in Dockerfile.base. If Dockerfile OPENCLAW_VERSION is updated but this constant is not, the patch will fail closed (correct) but with no automated detection of the mismatch.
  • Impact: Version drift between Dockerfile and preload constant goes undetected until QR rendering fails silently in sandbox. Manual coordination required.
  • Recommended action: Add build-time script that extracts OPENCLAW_VERSION from Dockerfile.base, downloads the OpenClaw renderer source at that version, computes SHA256, and verifies against the constant. Fail build on mismatch. Or generate the constant at build time and inject it.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read line 73: hardcoded hash. Dockerfile.base line 203: ARG OPENCLAW_VERSION=2026.6.10. No build script connects them.
  • Missing regression test: Add CI job that computes hash from actual OpenClaw npm tarball at Dockerfile version and compares to constant in whatsapp-qr-compact.ts.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read line 73: hardcoded hash. Dockerfile.base line 203: ARG OPENCLAW_VERSION=2026.6.10. No build script connects them.
  • Evidence: const REVIEWED_OPENCLAW_QR_TERMINAL_RENDERER_SHA256 = "f74865035a498389fe910b23537a7dffeaee1b05e044999d855b61c96af0ada7";

PRA-8 Resolve/justify — Function serialization via .toString() in template literal is Node-version dependent

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:215
  • Category: correctness
  • Problem: createOpenClawQrTerminalLoaderSource embeds functions via .toString() in a template literal (lines 215-225). Function.prototype.toString output is implementation-dependent (Node version, V8 version, formatting). This makes the generated loader source non-deterministic across Node versions.
  • Impact: ESM loader source differs across Node versions, potentially causing hash mismatch or syntax errors. Non-reproducible builds for security-critical loader.
  • Recommended action: Extract ESM loader to separate file (e.g., whatsapp-qr-compact-loader.ts) compiled alongside preload. Import and register via Module.register(fileUrl) instead of data: URL. This enables TypeScript checking on loader code and eliminates serialization fragility.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read createOpenClawQrTerminalLoaderSource at lines 215-255 — uses isOpenClawQrTerminalRendererSource.toString(), etc., embedded in template literal.
  • Missing regression test: Add build-time check that compiles loader source with multiple Node versions (or at least verifies the generated loader source is stable across runs).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read createOpenClawQrTerminalLoaderSource at lines 215-255 — uses isOpenClawQrTerminalRendererSource.toString(), etc., embedded in template literal.
  • Evidence: Template literal embeds: isOpenClawQrTerminalRendererSource.toString(), isReviewedOpenClawQrTerminalRendererIntegrity.toString(), describeOpenClawQrTerminalPatchSkip.toString(), patchOpenClawQrTerminalRendererSource.toString()

PRA-9 Resolve/justify — Module.register(data: URL) pattern unconventional vs Slack's Module.registerHooks

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:394
  • Category: architecture
  • Problem: Module.register(data: URL) pattern is unconventional. Slack's equivalent (slack-channel-guard.ts:273-274) uses Module.registerHooks({ load: ... }) with a direct function reference. The data: URL approach requires base64 encoding, prevents TypeScript checking of the loader, and is harder to debug.
  • Impact: Inconsistent pattern with existing codebase (Slack channel guard). Loader code cannot be type-checked. Harder to audit and debug loader behavior.
  • Recommended action: Align with Slack's approach: extract loader to separate .ts file, compile it, register via Module.registerHooks or Module.register(fileUrl). Document why data: URL is required if it is.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Compare whatsapp-qr-compact.ts line 394 (Module.register(data: URL)) with slack-channel-guard.ts line 273 (Module.registerHooks({ load: ... })).
  • Missing regression test: Add test that both registration patterns work on supported Node versions; document minimum Node version for each.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Compare whatsapp-qr-compact.ts line 394 (Module.register(data: URL)) with slack-channel-guard.ts line 273 (Module.registerHooks({ load: ... })).
  • Evidence: var loaderUrl = "data:text/javascript;base64," + Buffer.from(loaderSource, "utf8").toString("base64"); Module.register(loaderUrl);

PRA-10 Resolve/justify — Workaround removal condition lacks automated verification

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:40
  • Category: scope
  • Problem: Removal criterion comment (lines 40-42) says to drop this preload once bundled @openclaw/whatsapp renders scan-friendly QR by default. No automated verification exists — this is a manual verification comment that will likely be forgotten.
  • Impact: Preload persists indefinitely even after upstream fixes. No signal to remove dead code.
  • Recommended action: Add build-time or CI check that measures OpenClaw's default QR output (without preload) and fails if it meets compact criteria (rows <= 40, quiet zone >= 4). This creates an automated 'remove me' signal.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read comment at lines 40-42 — manual verification only. E2E test measures baseline QR but doesn't fail when baseline becomes compact.
  • Missing regression test: Modify E2E test to fail (or emit distinct signal) when baseline QR rows <= COMPACT_MAX_ROWS and quiet zones >= 4, indicating the workaround is no longer needed.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read comment at lines 40-42 — manual verification only. E2E test measures baseline QR but doesn't fail when baseline becomes compact.
  • Evidence: Comment: "Removal criterion: drop this preload... once every bundled @openclaw/whatsapp version renders a scan-friendly QR by default. Verify by pairing on a DGX Spark terminal and confirming the QR fits without this preload."

PRA-11 Resolve/justify — PR claims data:image/png fallback but test expects dataImageFallback=false

  • Location: test/e2e/live/whatsapp-qr-compact.test.ts:307
  • Category: acceptance
  • Problem: E2E test expects dataImageFallback: false for both loginDefault and explicitSmall profiles (lines 307, 315). The PR description and issue [Ubuntu 24.04][Agent&Skills] openclaw channels login --channel whatsapp renders QR code that cannot be scanned by WhatsApp or any QR reader #6386 mention data:image/png fallback as a feature, but the implementation renders via terminal escape codes (black-on-white blocks) with no data:image/png fallback. The test correctly reflects current behavior but contradicts claimed acceptance criteria.
  • Impact: Acceptance criteria in issue/PR description don't match implementation. Stakeholders may expect fallback that doesn't exist.
  • Recommended action: Either implement the data:image/png fallback in the preload and update test to expect true, or remove fallback mention from PR description and update issue [Ubuntu 24.04][Agent&Skills] openclaw channels login --channel whatsapp renders QR code that cannot be scanned by WhatsApp or any QR reader #6386 acceptance criteria to match actual implementation (terminal-only rendering with quiet zone).
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read E2E test lines 307 and 315: expect(patchedProbe.loginDefault.dataImageFallback).toBe(false). Search PR description/issue for "data:image/png" or "fallback" claims.
  • Missing regression test: Add explicit acceptance test that documents whether data:image/png fallback is required. If not required, remove from acceptance criteria.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read E2E test lines 307 and 315: expect(patchedProbe.loginDefault.dataImageFallback).toBe(false). Search PR description/issue for "data:image/png" or "fallback" claims.
  • Evidence: PR body: "The preload does not print a credential-derived data:image/png fallback URL." E2E test asserts dataImageFallback === false

PRA-12 Resolve/justify — Missing test for Module.register failure diagnostic emission

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts:1
  • Category: tests
  • Problem: Unit tests cover diagnostic emission for source rewrite skip (test line 267-278) but not for Module.register failure path. The warnWhatsappQrCompact call in installOpenClawQrTerminalSourceLoader catch block (line 396) is not tested.
  • Impact: Critical failure path (ESM loader registration failure) has no test coverage. Regression could silently disable diagnostics.
  • Recommended action: Add test using vi.spyOn(process.stderr, 'write') or capturing stderr to assert diagnostic emission on Module.register failure. Mock Module.register to throw or be undefined.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Search whatsapp-qr-compact.test.ts for Module.register failure test — none exists. Only source rewrite skip diagnostic is tested.
  • Missing regression test: Add test: mock Module.register to throw, verify warnWhatsappQrCompact called with registration failure message.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Search whatsapp-qr-compact.test.ts for Module.register failure test — none exists. Only source rewrite skip diagnostic is tested.
  • Evidence: Test "emits non-secret loader diagnostics when the source rewrite is skipped" exists but no equivalent for Module.register failure

PRA-13 Resolve/justify — No actual QR decode validation — only proxy metrics

  • Location: test/e2e/live/whatsapp-qr-compact.test.ts:1
  • Category: acceptance
  • Problem: Acceptance criterion "QR scannable by WhatsApp/3rd-party scanners" is verified only by row/column counts and quiet zone measurements. No actual QR decode validation with zxing/zbar or real scanner to prove the rendered QR is scannable.
  • Impact: Proxy metrics (rows, quiet zones) don't guarantee scannability. Real-world scanning could fail due to contrast, encoding, or module rendering issues not captured by dimensions.
  • Recommended action: Add headless browser test that renders QR in terminal, captures screenshot, and validates with zxing/zbar decoder. Clarify fallback requirement: implement or remove from acceptance criteria.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: E2E test only measures terminal dimensions and quiet zones. No QR decoder validation.
  • Missing regression test: Add integration test using qrcode-reader or zxing to decode the rendered terminal QR and verify payload matches.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: E2E test only measures terminal dimensions and quiet zones. No QR decoder validation.
  • Evidence: PROBE_SOURCE profiles QR with rows, cols, quietEdges but no decode attempt

PRA-14 Resolve/justify — CJS and ESM detection paths not verified for parity

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:65
  • Category: architecture
  • Problem: Detection logic (isOpenClawQrTerminalRendererSource) is shared between CJS hook (string-based) and ESM loader (same function serialized via toString). No test verifies both paths produce identical detection results on same input.
  • Impact: Divergent detection behavior between CJS and ESM paths could cause one path to patch while the other skips, leading to inconsistent QR rendering.
  • Recommended action: Add test that both CJS hook and ESM loader produce identical detection results on same renderer source. Consider separating detection from patching for clearer boundaries.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read isOpenClawQrTerminalRendererSource used in both resolvePatchedModule (CJS) and createOpenClawQrTerminalLoaderSource (ESM). No test compares both paths.
  • Missing regression test: Add test that feeds identical renderer source to both CJS patch path and ESM loader path and asserts identical patch/skip decisions.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read isOpenClawQrTerminalRendererSource used in both resolvePatchedModule (CJS) and createOpenClawQrTerminalLoaderSource (ESM). No test compares both paths.
  • Evidence: isOpenClawQrTerminalRendererSource exported and used in both resolvePatchedModule (line 340) and createOpenClawQrTerminalLoaderSource (line 219)

PRA-15 Resolve/justify — No Node version validation for Module.register ESM loader support

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:376
  • Category: security
  • Problem: installOpenClawQrTerminalSourceLoader checks Module.register availability and warns if unavailable, but does not validate that the Node version supports the ESM loader hook API. Module.register exists since Node 18.19.0 / 20.6.0 but behavior varies. No version check means the warning may fire on supported versions due to other issues (permissions, --experimental-loader flag), or silently succeed on versions where the hook doesn't work as expected.
  • Impact: False warnings or silent failures on edge Node versions. Operators can't distinguish "Node too old" from "permission denied" from "loader bug".
  • Recommended action: Add Node version check: require Node >= 18.19.0 || >= 20.6.0 for ESM loader registration. If below, emit actionable warning with minimum version. Consider making this a hard requirement for sandbox environments.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read installOpenClawQrTerminalSourceLoader lines 376-398 — only checks typeof Module.register === 'function', no version validation.
  • Missing regression test: Add test that simulates Node version below/above threshold and verifies correct warning/registration behavior.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read installOpenClawQrTerminalSourceLoader lines 376-398 — only checks typeof Module.register === 'function', no version validation.
  • Evidence: if (!Module || typeof Module.register !== "function") { warn...; return; } — no process.version check

PRA-16 Resolve/justify — Monolith growth: file grew 240 lines (128%) handling three patch targets

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1
  • Category: architecture
  • Problem: File grew from 187 to 427 lines (+240, 128% increase). The preload now handles three distinct patch targets (qrcode, qrcode-terminal, OpenClaw ES module) with separate logic, detection, and loader generation. This violates single responsibility and makes review harder.
  • Impact: Reduced reviewability, higher cognitive load, increased risk of bugs in one area affecting others. Harder to test in isolation.
  • Recommended action: Split into multiple files: whatsapp-qr-compact.ts (main entry, installs hooks), whatsapp-qr-compact-qrcode.ts (qrcode/qrcode-terminal patches), whatsapp-qr-compact-openclaw.ts (OpenClaw ES module detection/patch/loader), whatsapp-qr-compact-diagnostics.ts (warnWhatsappQrCompact). Keep exports for testability.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Drift evidence shows monolithDeltas severity blocker for whatsapp-qr-compact.ts: baseLines 187, headLines 427, delta 240.
  • Missing regression test: Ensure all existing tests pass after split; no behavior change.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Drift evidence shows monolithDeltas severity blocker for whatsapp-qr-compact.ts: baseLines 187, headLines 427, delta 240.
  • Evidence: Single file contains: CJS hook (qrcode/qrcode-terminal), ESM loader generation, OpenClaw renderer detection/patch, diagnostics, self-install logic

PRA-20 Resolve/justify — Source-of-truth: Module.register failure catch lacks regression test and removal condition enforcement

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:395
  • Category: correctness
  • Problem: The catch block at line 395 handles Module.register failure as a localized workaround for Node version/permission limitations. No regression test exists for this failure path, and the removal condition (Node >=20.6 guaranteed in sandbox) is not enforced.
  • Impact: Workaround persists without automated verification. Could mask real issues or be removed prematurely.
  • Recommended action: Add test for Module.register failure path (see PRA-11). Enforce minimum Node version in sandbox (see PRA-20) to make removal condition actionable.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Line 395: } catch (_e) { warnWhatsappQrCompact(...); } — no test, no version gate
  • Missing regression test: Add test mocking Module.register to throw; verify warnWhatsappQrCompact called. Add sandbox Node version check.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Line 395: } catch (_e) { warnWhatsappQrCompact(...); } — no test, no version gate.
  • Evidence: installOpenClawQrTerminalSourceLoader catch block at line 395; no test covers this path

PRA-21 Resolve/justify — Source-of-truth: patchQrcode catch returns unpatched module on any error

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:299
  • Category: correctness
  • Problem: The catch block at line 299 in patchQrcode returns the original unpatched module on any error during wrapper execution. This is a fail-open behavior for the patching logic.
  • Impact: Any unexpected error in the compact renderer (e.g., modules data malformed) causes silent fallback to unpatched qrcode.toString, potentially rendering oversized QR.
  • Recommended action: This is a defensive boundary at the module object trust boundary. Keep fail-open but add test with malformed modules to verify behavior. Consider structured error logging.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Line 299: } catch (err) { cb(err); return undefined; } — catches renderQrcodePackageTerminal errors
  • Missing regression test: Add test with mod.create returning malformed modules; verify callback receives error and unpatched path not taken.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Line 299: } catch (err) { cb(err); return undefined; } — catches renderQrcodePackageTerminal errors.
  • Evidence: patchQrcode wrapper calls renderQrcodePackageTerminal which can throw; catch returns undefined for callback path

PRA-22 Resolve/justify — Source-of-truth: resolvePatchedModule catch returns unpatched module on any error

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:345
  • Category: correctness
  • Problem: The catch block at line 345 in resolvePatchedModule returns the loaded module unpatched on any error during shape detection or patching. This is a fail-open behavior at the Module._load hook boundary.
  • Impact: Any error in detection/patching (including programming errors) silently disables the preload for that module load. Could hide bugs.
  • Recommended action: This is a legitimate defensive boundary at the Module._load hook — a bug in the preload must not break all qrcode loads. Keep but ensure errors are logged (they are via warnWhatsappQrCompact in patch functions). Add test verifying error in detection doesn't crash Module._load.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Line 345: } catch (_e) { return loaded; } in resolvePatchedModule
  • Missing regression test: Add test throwing in isQrcodePackage; verify loaded module returned unpatched and no crash.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Line 345: } catch (_e) { return loaded; } in resolvePatchedModule.
  • Evidence: resolvePatchedModule wraps isQrcodePackage/isQrcodeTerminalPackage/patchQrcode/patchQrcodeTerminal in try/catch

💡 In-scope improvements

These are lower-risk, not throwaway. Prefer fixing them in this PR when they are local to changed code; defer only with rationale or a linked follow-up.

PRA-17 Improvement — Replace hasOwn helper with Object.hasOwn (stdlib)

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:20
  • Category: correctness
  • Problem: hasOwn helper (lines 20-24) duplicates Object.hasOwn (ES2022, available in Node 16.9+). The project targets ES2022 per tsconfig.
  • Impact: Unnecessary custom utility where stdlib equivalent exists.
  • Suggested action: Replace hasOwn(mod, name) calls with Object.hasOwn(mod, name). Delete helper.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read lines 20-24: function hasOwn(mod, name) { return mod && Object.prototype.hasOwnProperty.call(mod, name); }
  • Missing regression test: None needed — stdlib replacement with identical semantics.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: hasOwn called at lines 50, 57, 60 in isQrcodePackage and isQrcodeTerminalPackage

PRA-18 Improvement — Simplify markPatched with Symbol key

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:17
  • Category: correctness
  • Problem: markPatched uses __nemoclawCompactPatched property name which could theoretically collide. Symbol key eliminates collision risk.
  • Impact: Theoretical collision risk with other code adding same property name. Symbol is the idiomatic solution.
  • Suggested action: Use const PATCHED = Symbol('nemoclawCompactPatched'); then mod[PATCHED] = true; check via mod[PATCHED].
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read markPatched function at lines 17-24 — uses string property name.
  • Missing regression test: None needed — refactor with identical semantics.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: markPatched defines __nemoclawCompactPatched; checked via mod.__nemoclawCompactPatched in patchQrcode and patchQrcodeTerminal

PRA-19 Improvement — Replace for...in opts merge with Object.fromEntries filter (stdlib)

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:128
  • Category: correctness
  • Problem: cloneQrcodeCreateOptions uses for...in loop with hasOwn check (lines 128-134). Object.assign with filtered keys is simpler and stdlib.
  • Impact: Verbose manual iteration where stdlib one-liner is clearer and less error-prone.
  • Suggested action: Replace with const createOpts = Object.fromEntries(Object.entries(opts).filter(([k]) => k !== 'type' && k !== 'small')); or similar.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read cloneQrcodeCreateOptions at lines 128-134 — for...in with hasOwn.
  • Missing regression test: None needed — stdlib replacement with identical semantics.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: for (var key in opts) { if (!Object.prototype.hasOwnProperty.call(opts, key)) continue; if (key === "type" || key === "small") continue; createOpts[key] = opts[key]; }
Simplification opportunities: 3 possible cuts, net -12 lines possible

These are safe simplification checks only. Do not remove validation, security controls, data-loss prevention, or required tests.

  • PRA-17 stdlib (src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:20): function hasOwn(mod, name) { return mod && Object.prototype.hasOwnProperty.call(mod, name); }
    • Replacement: Object.hasOwn(mod, name)
    • Net: -5 lines
    • Safety boundary: No behavior change — Object.hasOwn has identical semantics for own property checking
  • PRA-18 native (src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:17): function markPatched(mod) { try { Object.defineProperty(mod, "__nemoclawCompactPatched", { value: true }); } catch (_e) { mod.__nemoclawCompactPatched = true; } }
    • Replacement: const PATCHED = Symbol('nemoclawCompactPatched'); function markPatched(mod) { mod[PATCHED] = true; } function isPatched(mod) { return mod[PATCHED] === true; }
    • Net: -3 lines
    • Safety boundary: Symbol keys are non-enumerable and collision-proof; no change to patching logic
  • PRA-19 stdlib (src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:128): function cloneQrcodeCreateOptions(opts) { var createOpts = {}; for (var key in opts) { if (!Object.prototype.hasOwnProperty.call(opts, key)) continue; if (key === "type" || key === "small") continue; createOpts[key] = opts[key]; } return createOpts; }
    • Replacement: function cloneQrcodeCreateOptions(opts) { return Object.fromEntries(Object.entries(opts).filter(([k]) => k !== 'type' && k !== 'small')); }
    • Net: -4 lines
    • Safety boundary: Same filtering logic; Object.entries only returns own enumerable properties
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Runtime validation — whatsapp-qr-compact: Module.register unavailable → hard failure in sandbox with actionable message. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts. Critical gaps in failure-path testing and actual QR decode verification.
  • PRA-T2 Runtime validation — whatsapp-qr-compact: Node v18.18 → ESM loader skipped with actionable version warning. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts. Critical gaps in failure-path testing and actual QR decode verification.
  • PRA-T3 Runtime validation — whatsapp-qr-compact: rendered terminal QR decodes via zxing to original payload. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts. Critical gaps in failure-path testing and actual QR decode verification.
  • PRA-T4 Runtime validation — whatsapp-qr-compact: CJS and ESM detection produce identical patch/skip decision on same renderer source. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts. Critical gaps in failure-path testing and actual QR decode verification.
  • PRA-T5 Runtime validation — whatsapp-qr-compact: minified OpenClaw renderer source detected or fails closed with warning. Runtime/sandbox/infrastructure paths need behavioral runtime validation: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts. Critical gaps in failure-path testing and actual QR decode verification.
  • PRA-T6 Unit test runs TypeScript source via --require without TypeScript loader — Update unit test to compile whatsapp-qr-compact.ts via tsconfig.runtime-preloads.json and require the .js output, matching the E2E test's compileProductionPreload pattern. Or configure vitest to transpile the preload for unit tests.
  • PRA-T7 Missing test for Module.register failure diagnostic emission — Add test using vi.spyOn(process.stderr, 'write') or capturing stderr to assert diagnostic emission on Module.register failure. Mock Module.register to throw or be undefined.
  • PRA-T8 Acceptance clause — QR code is scannable by WhatsApp/3rd-party scanners — add test evidence or identify existing coverage. E2E test validates quiet zones >=4 modules on all edges and rows <=40, but no actual QR decode validation with zxing/zbar
Since last review details

Current findings, using the urgency labels above:

PRA-1 Resolve/justify — Source-of-truth review needed: patchQrcode render error catch (line 299)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as needs_followup.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: Add test with mod.create returning malformed modules; verify callback receives error
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: } catch (err) { cb(err); return undefined; } in callback path

PRA-2 Resolve/justify — Source-of-truth review needed: installOpenClawQrTerminalSourceLoader Module.register catch (line 395)

  • Location: not file-specific
  • Category: architecture
  • Problem: The advisor marked localized patch analysis as missing.
  • Impact: A localized workaround can preserve or hide an invalid state when the source boundary is unclear.
  • Recommended action: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Missing regression test: MISSING — Add test mocking Module.register to throw/undefined; verify warning emitted and behavior documented
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Inspect the localized patch and source-of-truth review fields for a concrete invalid state, source boundary, source-fix constraint, regression test, and removal condition.
  • Evidence: try { Module.register(loaderUrl); } catch (_e) { warnWhatsappQrCompact(...); } — no test, no version gate

PRA-3 Required — @ts-nocheck disables all TypeScript checking for security-critical sandbox preload

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1
  • Category: security
  • Problem: File-wide // @ts-nocheck directive prevents TypeScript from checking any code in this security-critical sandbox preload. Type errors in patch logic, hash verification, or Module.register handling would not be caught at compile time.
  • Impact: Security-critical preload runs with zero compile-time type safety. Errors in hash verification, source patching, or Module.register failure handling could reach production undetected.
  • Required action: Remove // @ts-nocheck and fix any type errors. If intentional for JS compatibility, use // @ts-expect-error on specific lines with justification comments.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read line 1 of whatsapp-qr-compact.ts — // @ts-nocheck is present. Run tsc -p tsconfig.runtime-preloads.json to see suppressed errors.
  • Missing regression test: Add build step that runs tsc --noEmit on the preload source and fails on any error; this prevents reintroduction of @ts-nocheck.
  • Done when: The required change is committed and verification passes: Read line 1 of whatsapp-qr-compact.ts — // @ts-nocheck is present. Run tsc -p tsconfig.runtime-preloads.json to see suppressed errors.
  • Evidence: // @ts-nocheck at line 1 of whatsapp-qr-compact.ts; tsconfig.runtime-preloads.json has strict: true but nocheck bypasses it

PRA-4 Required — Unit test runs TypeScript source via --require without TypeScript loader

  • Location: test/whatsapp-qr-compact.test.ts:338
  • Category: tests
  • Problem: Unit test spawns raw Node process with --require pointing to .ts source file (PRELOAD_SOURCE). Node cannot natively execute TypeScript; this test only passes if a global TypeScript loader (ts-node, vitest oxc) happens to be registered via NODE_OPTIONS, which is not guaranteed in the spawned process.
  • Impact: Test validity depends on ambient TypeScript loader. In clean CI environments without NODE_OPTIONS, the test would fail or silently test nothing. False confidence in preload behavior.
  • Required action: Update unit test to compile whatsapp-qr-compact.ts via tsconfig.runtime-preloads.json and require the .js output, matching the E2E test's compileProductionPreload pattern. Or configure vitest to transpile the preload for unit tests.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read test/whatsapp-qr-compact.ts runProbe function: spawnSync(process.execPath, ["--require", PRELOAD_SOURCE, ...]) where PRELOAD_SOURCE ends in .ts. No TypeScript loader is configured for the spawned process.
  • Missing regression test: Add CI check that runs the unit test in a clean Node environment (no NODE_OPTIONS) and verifies it passes — this proves the test doesn't rely on ambient TypeScript loaders.
  • Done when: The required change is committed and verification passes: Read test/whatsapp-qr-compact.ts runProbe function: spawnSync(process.execPath, ["--require", PRELOAD_SOURCE, ...]) where PRELOAD_SOURCE ends in .ts. No TypeScript loader is configured for the spawned process.
  • Evidence: PRELOAD_SOURCE points to .ts file; spawnSync uses raw process.execPath without -r ts-node/register or similar

PRA-5 Required — Module.register failure silently disables critical ESM patch with only warning

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:387
  • Category: security
  • Problem: Module.register(loaderUrl) try/catch at lines 391-397 now emits a warning via warnWhatsappQrCompact on failure, but the ESM loader registration is still silently skipped. The OpenClaw QR renderer ES module patch (quiet-zone widening on all four edges) is completely disabled with only a stderr warning. In sandbox environments where Module.register is unavailable (older Node, missing --experimental-loader, permissions), the critical quiet-zone patch never applies.
  • Impact: Operators never learn the security patch failed. OpenClaw QR renderer runs unpatched → oversized QR with no data:image/png fallback. WhatsApp pairing fails silently on Node versions without Module.register support or in restricted environments.
  • Required action: In sandbox contexts, make Module.register failure a hard failure (process.exit with clear message). At minimum, emit a structured diagnostic that includes the Node version and Module.register availability, and consider falling back to a CJS-based patch if feasible.
  • Expected follow-up: Fix before merge or get explicit maintainer override.
  • Verification: Read installOpenClawQrTerminalSourceLoader function lines 376-398: try { Module.register(loaderUrl); } catch (_e) { warnWhatsappQrCompact(...); } — no hard failure, no fallback, ESM patch simply disabled.
  • Missing regression test: Add test asserting process.exit or structured error emission when Module.register is unavailable (e.g., by mocking Module.register = undefined). Verify CJS hook still works but ESM path is explicitly failed.
  • Done when: The required change is committed and verification passes: Read installOpenClawQrTerminalSourceLoader function lines 376-398: try { Module.register(loaderUrl); } catch (_e) { warnWhatsappQrCompact(...); } — no hard failure, no fallback, ESM patch simply disabled.
  • Evidence: Lines 391-397: try { Module.register(loaderUrl); } catch (_e) { warnWhatsappQrCompact("OpenClaw QR renderer source loader registration failed; explicit compact quiet-zone rewrite skipped"); }

PRA-6 Resolve/justify — Fragile string-based renderer detection via indexOf checks

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:65
  • Category: correctness
  • Problem: isOpenClawQrTerminalRendererSource uses string indexOf checks for function names and patterns (renderCompactTerminalQr, COMPACT_MARGIN_MODULES, async function renderQrTerminal, opts.small === true). This is fragile: minification, formatting changes, or upstream refactors will cause silent detection failure and the patch will be skipped.
  • Impact: OpenClaw renderer updates (even whitespace/formatting) cause silent patch skip. No automated detection of drift — only manual investigation when QR becomes unscannable.
  • Recommended action: Pin detection to specific OpenClaw version/hash. Compute expected source hash at build time (from pinned @openclaw/whatsapp version in Dockerfile) and verify at runtime before detection. Fall back to string detection only with warning. Or use AST-based detection for robustness.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read isOpenClawQrTerminalRendererSource at lines 65-72 — four indexOf checks on source strings.
  • Missing regression test: Add test that varies whitespace, formatting, and minification of the renderer source and verifies detection behavior (should either detect reliably or fail closed with warning).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read isOpenClawQrTerminalRendererSource at lines 65-72 — four indexOf checks on source strings.
  • Evidence: Function returns true only if all four indexOf checks pass: renderCompactTerminalQr, COMPACT_MARGIN_MODULES, async function renderQrTerminal, opts.small === true

PRA-7 Resolve/justify — Hardcoded SHA256 hash not build-verified against Dockerfile OPENCLAW_VERSION

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:73
  • Category: correctness
  • Problem: REVIEWED_OPENCLAW_QR_TERMINAL_RENDERER_SHA256 is hardcoded constant. No build-time verification that this hash matches the actual OpenClaw 2026.6.10 renderer source bundled in Dockerfile.base. If Dockerfile OPENCLAW_VERSION is updated but this constant is not, the patch will fail closed (correct) but with no automated detection of the mismatch.
  • Impact: Version drift between Dockerfile and preload constant goes undetected until QR rendering fails silently in sandbox. Manual coordination required.
  • Recommended action: Add build-time script that extracts OPENCLAW_VERSION from Dockerfile.base, downloads the OpenClaw renderer source at that version, computes SHA256, and verifies against the constant. Fail build on mismatch. Or generate the constant at build time and inject it.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read line 73: hardcoded hash. Dockerfile.base line 203: ARG OPENCLAW_VERSION=2026.6.10. No build script connects them.
  • Missing regression test: Add CI job that computes hash from actual OpenClaw npm tarball at Dockerfile version and compares to constant in whatsapp-qr-compact.ts.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read line 73: hardcoded hash. Dockerfile.base line 203: ARG OPENCLAW_VERSION=2026.6.10. No build script connects them.
  • Evidence: const REVIEWED_OPENCLAW_QR_TERMINAL_RENDERER_SHA256 = "f74865035a498389fe910b23537a7dffeaee1b05e044999d855b61c96af0ada7";

PRA-8 Resolve/justify — Function serialization via .toString() in template literal is Node-version dependent

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:215
  • Category: correctness
  • Problem: createOpenClawQrTerminalLoaderSource embeds functions via .toString() in a template literal (lines 215-225). Function.prototype.toString output is implementation-dependent (Node version, V8 version, formatting). This makes the generated loader source non-deterministic across Node versions.
  • Impact: ESM loader source differs across Node versions, potentially causing hash mismatch or syntax errors. Non-reproducible builds for security-critical loader.
  • Recommended action: Extract ESM loader to separate file (e.g., whatsapp-qr-compact-loader.ts) compiled alongside preload. Import and register via Module.register(fileUrl) instead of data: URL. This enables TypeScript checking on loader code and eliminates serialization fragility.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read createOpenClawQrTerminalLoaderSource at lines 215-255 — uses isOpenClawQrTerminalRendererSource.toString(), etc., embedded in template literal.
  • Missing regression test: Add build-time check that compiles loader source with multiple Node versions (or at least verifies the generated loader source is stable across runs).
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read createOpenClawQrTerminalLoaderSource at lines 215-255 — uses isOpenClawQrTerminalRendererSource.toString(), etc., embedded in template literal.
  • Evidence: Template literal embeds: isOpenClawQrTerminalRendererSource.toString(), isReviewedOpenClawQrTerminalRendererIntegrity.toString(), describeOpenClawQrTerminalPatchSkip.toString(), patchOpenClawQrTerminalRendererSource.toString()

PRA-9 Resolve/justify — Module.register(data: URL) pattern unconventional vs Slack's Module.registerHooks

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:394
  • Category: architecture
  • Problem: Module.register(data: URL) pattern is unconventional. Slack's equivalent (slack-channel-guard.ts:273-274) uses Module.registerHooks({ load: ... }) with a direct function reference. The data: URL approach requires base64 encoding, prevents TypeScript checking of the loader, and is harder to debug.
  • Impact: Inconsistent pattern with existing codebase (Slack channel guard). Loader code cannot be type-checked. Harder to audit and debug loader behavior.
  • Recommended action: Align with Slack's approach: extract loader to separate .ts file, compile it, register via Module.registerHooks or Module.register(fileUrl). Document why data: URL is required if it is.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Compare whatsapp-qr-compact.ts line 394 (Module.register(data: URL)) with slack-channel-guard.ts line 273 (Module.registerHooks({ load: ... })).
  • Missing regression test: Add test that both registration patterns work on supported Node versions; document minimum Node version for each.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Compare whatsapp-qr-compact.ts line 394 (Module.register(data: URL)) with slack-channel-guard.ts line 273 (Module.registerHooks({ load: ... })).
  • Evidence: var loaderUrl = "data:text/javascript;base64," + Buffer.from(loaderSource, "utf8").toString("base64"); Module.register(loaderUrl);

PRA-10 Resolve/justify — Workaround removal condition lacks automated verification

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:40
  • Category: scope
  • Problem: Removal criterion comment (lines 40-42) says to drop this preload once bundled @openclaw/whatsapp renders scan-friendly QR by default. No automated verification exists — this is a manual verification comment that will likely be forgotten.
  • Impact: Preload persists indefinitely even after upstream fixes. No signal to remove dead code.
  • Recommended action: Add build-time or CI check that measures OpenClaw's default QR output (without preload) and fails if it meets compact criteria (rows <= 40, quiet zone >= 4). This creates an automated 'remove me' signal.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read comment at lines 40-42 — manual verification only. E2E test measures baseline QR but doesn't fail when baseline becomes compact.
  • Missing regression test: Modify E2E test to fail (or emit distinct signal) when baseline QR rows <= COMPACT_MAX_ROWS and quiet zones >= 4, indicating the workaround is no longer needed.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read comment at lines 40-42 — manual verification only. E2E test measures baseline QR but doesn't fail when baseline becomes compact.
  • Evidence: Comment: "Removal criterion: drop this preload... once every bundled @openclaw/whatsapp version renders a scan-friendly QR by default. Verify by pairing on a DGX Spark terminal and confirming the QR fits without this preload."

PRA-11 Resolve/justify — PR claims data:image/png fallback but test expects dataImageFallback=false

  • Location: test/e2e/live/whatsapp-qr-compact.test.ts:307
  • Category: acceptance
  • Problem: E2E test expects dataImageFallback: false for both loginDefault and explicitSmall profiles (lines 307, 315). The PR description and issue [Ubuntu 24.04][Agent&Skills] openclaw channels login --channel whatsapp renders QR code that cannot be scanned by WhatsApp or any QR reader #6386 mention data:image/png fallback as a feature, but the implementation renders via terminal escape codes (black-on-white blocks) with no data:image/png fallback. The test correctly reflects current behavior but contradicts claimed acceptance criteria.
  • Impact: Acceptance criteria in issue/PR description don't match implementation. Stakeholders may expect fallback that doesn't exist.
  • Recommended action: Either implement the data:image/png fallback in the preload and update test to expect true, or remove fallback mention from PR description and update issue [Ubuntu 24.04][Agent&Skills] openclaw channels login --channel whatsapp renders QR code that cannot be scanned by WhatsApp or any QR reader #6386 acceptance criteria to match actual implementation (terminal-only rendering with quiet zone).
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read E2E test lines 307 and 315: expect(patchedProbe.loginDefault.dataImageFallback).toBe(false). Search PR description/issue for "data:image/png" or "fallback" claims.
  • Missing regression test: Add explicit acceptance test that documents whether data:image/png fallback is required. If not required, remove from acceptance criteria.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read E2E test lines 307 and 315: expect(patchedProbe.loginDefault.dataImageFallback).toBe(false). Search PR description/issue for "data:image/png" or "fallback" claims.
  • Evidence: PR body: "The preload does not print a credential-derived data:image/png fallback URL." E2E test asserts dataImageFallback === false

PRA-12 Resolve/justify — Missing test for Module.register failure diagnostic emission

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts:1
  • Category: tests
  • Problem: Unit tests cover diagnostic emission for source rewrite skip (test line 267-278) but not for Module.register failure path. The warnWhatsappQrCompact call in installOpenClawQrTerminalSourceLoader catch block (line 396) is not tested.
  • Impact: Critical failure path (ESM loader registration failure) has no test coverage. Regression could silently disable diagnostics.
  • Recommended action: Add test using vi.spyOn(process.stderr, 'write') or capturing stderr to assert diagnostic emission on Module.register failure. Mock Module.register to throw or be undefined.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Search whatsapp-qr-compact.test.ts for Module.register failure test — none exists. Only source rewrite skip diagnostic is tested.
  • Missing regression test: Add test: mock Module.register to throw, verify warnWhatsappQrCompact called with registration failure message.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Search whatsapp-qr-compact.test.ts for Module.register failure test — none exists. Only source rewrite skip diagnostic is tested.
  • Evidence: Test "emits non-secret loader diagnostics when the source rewrite is skipped" exists but no equivalent for Module.register failure

PRA-13 Resolve/justify — No actual QR decode validation — only proxy metrics

  • Location: test/e2e/live/whatsapp-qr-compact.test.ts:1
  • Category: acceptance
  • Problem: Acceptance criterion "QR scannable by WhatsApp/3rd-party scanners" is verified only by row/column counts and quiet zone measurements. No actual QR decode validation with zxing/zbar or real scanner to prove the rendered QR is scannable.
  • Impact: Proxy metrics (rows, quiet zones) don't guarantee scannability. Real-world scanning could fail due to contrast, encoding, or module rendering issues not captured by dimensions.
  • Recommended action: Add headless browser test that renders QR in terminal, captures screenshot, and validates with zxing/zbar decoder. Clarify fallback requirement: implement or remove from acceptance criteria.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: E2E test only measures terminal dimensions and quiet zones. No QR decoder validation.
  • Missing regression test: Add integration test using qrcode-reader or zxing to decode the rendered terminal QR and verify payload matches.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: E2E test only measures terminal dimensions and quiet zones. No QR decoder validation.
  • Evidence: PROBE_SOURCE profiles QR with rows, cols, quietEdges but no decode attempt

PRA-14 Resolve/justify — CJS and ESM detection paths not verified for parity

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:65
  • Category: architecture
  • Problem: Detection logic (isOpenClawQrTerminalRendererSource) is shared between CJS hook (string-based) and ESM loader (same function serialized via toString). No test verifies both paths produce identical detection results on same input.
  • Impact: Divergent detection behavior between CJS and ESM paths could cause one path to patch while the other skips, leading to inconsistent QR rendering.
  • Recommended action: Add test that both CJS hook and ESM loader produce identical detection results on same renderer source. Consider separating detection from patching for clearer boundaries.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read isOpenClawQrTerminalRendererSource used in both resolvePatchedModule (CJS) and createOpenClawQrTerminalLoaderSource (ESM). No test compares both paths.
  • Missing regression test: Add test that feeds identical renderer source to both CJS patch path and ESM loader path and asserts identical patch/skip decisions.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read isOpenClawQrTerminalRendererSource used in both resolvePatchedModule (CJS) and createOpenClawQrTerminalLoaderSource (ESM). No test compares both paths.
  • Evidence: isOpenClawQrTerminalRendererSource exported and used in both resolvePatchedModule (line 340) and createOpenClawQrTerminalLoaderSource (line 219)

PRA-15 Resolve/justify — No Node version validation for Module.register ESM loader support

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:376
  • Category: security
  • Problem: installOpenClawQrTerminalSourceLoader checks Module.register availability and warns if unavailable, but does not validate that the Node version supports the ESM loader hook API. Module.register exists since Node 18.19.0 / 20.6.0 but behavior varies. No version check means the warning may fire on supported versions due to other issues (permissions, --experimental-loader flag), or silently succeed on versions where the hook doesn't work as expected.
  • Impact: False warnings or silent failures on edge Node versions. Operators can't distinguish "Node too old" from "permission denied" from "loader bug".
  • Recommended action: Add Node version check: require Node >= 18.19.0 || >= 20.6.0 for ESM loader registration. If below, emit actionable warning with minimum version. Consider making this a hard requirement for sandbox environments.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Read installOpenClawQrTerminalSourceLoader lines 376-398 — only checks typeof Module.register === 'function', no version validation.
  • Missing regression test: Add test that simulates Node version below/above threshold and verifies correct warning/registration behavior.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Read installOpenClawQrTerminalSourceLoader lines 376-398 — only checks typeof Module.register === 'function', no version validation.
  • Evidence: if (!Module || typeof Module.register !== "function") { warn...; return; } — no process.version check

PRA-16 Resolve/justify — Monolith growth: file grew 240 lines (128%) handling three patch targets

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:1
  • Category: architecture
  • Problem: File grew from 187 to 427 lines (+240, 128% increase). The preload now handles three distinct patch targets (qrcode, qrcode-terminal, OpenClaw ES module) with separate logic, detection, and loader generation. This violates single responsibility and makes review harder.
  • Impact: Reduced reviewability, higher cognitive load, increased risk of bugs in one area affecting others. Harder to test in isolation.
  • Recommended action: Split into multiple files: whatsapp-qr-compact.ts (main entry, installs hooks), whatsapp-qr-compact-qrcode.ts (qrcode/qrcode-terminal patches), whatsapp-qr-compact-openclaw.ts (OpenClaw ES module detection/patch/loader), whatsapp-qr-compact-diagnostics.ts (warnWhatsappQrCompact). Keep exports for testability.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Drift evidence shows monolithDeltas severity blocker for whatsapp-qr-compact.ts: baseLines 187, headLines 427, delta 240.
  • Missing regression test: Ensure all existing tests pass after split; no behavior change.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Drift evidence shows monolithDeltas severity blocker for whatsapp-qr-compact.ts: baseLines 187, headLines 427, delta 240.
  • Evidence: Single file contains: CJS hook (qrcode/qrcode-terminal), ESM loader generation, OpenClaw renderer detection/patch, diagnostics, self-install logic

PRA-17 Improvement — Replace hasOwn helper with Object.hasOwn (stdlib)

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:20
  • Category: correctness
  • Problem: hasOwn helper (lines 20-24) duplicates Object.hasOwn (ES2022, available in Node 16.9+). The project targets ES2022 per tsconfig.
  • Impact: Unnecessary custom utility where stdlib equivalent exists.
  • Suggested action: Replace hasOwn(mod, name) calls with Object.hasOwn(mod, name). Delete helper.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read lines 20-24: function hasOwn(mod, name) { return mod && Object.prototype.hasOwnProperty.call(mod, name); }
  • Missing regression test: None needed — stdlib replacement with identical semantics.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: hasOwn called at lines 50, 57, 60 in isQrcodePackage and isQrcodeTerminalPackage

PRA-18 Improvement — Simplify markPatched with Symbol key

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:17
  • Category: correctness
  • Problem: markPatched uses __nemoclawCompactPatched property name which could theoretically collide. Symbol key eliminates collision risk.
  • Impact: Theoretical collision risk with other code adding same property name. Symbol is the idiomatic solution.
  • Suggested action: Use const PATCHED = Symbol('nemoclawCompactPatched'); then mod[PATCHED] = true; check via mod[PATCHED].
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read markPatched function at lines 17-24 — uses string property name.
  • Missing regression test: None needed — refactor with identical semantics.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: markPatched defines __nemoclawCompactPatched; checked via mod.__nemoclawCompactPatched in patchQrcode and patchQrcodeTerminal

PRA-19 Improvement — Replace for...in opts merge with Object.fromEntries filter (stdlib)

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:128
  • Category: correctness
  • Problem: cloneQrcodeCreateOptions uses for...in loop with hasOwn check (lines 128-134). Object.assign with filtered keys is simpler and stdlib.
  • Impact: Verbose manual iteration where stdlib one-liner is clearer and less error-prone.
  • Suggested action: Replace with const createOpts = Object.fromEntries(Object.entries(opts).filter(([k]) => k !== 'type' && k !== 'small')); or similar.
  • Expected follow-up: Prefer a current-PR fix when local to changed code; defer only with rationale or linked follow-up.
  • Verification: Read cloneQrcodeCreateOptions at lines 128-134 — for...in with hasOwn.
  • Missing regression test: None needed — stdlib replacement with identical semantics.
  • Done when: The local improvement is applied, or the PR notes why it should be deferred.
  • Evidence: for (var key in opts) { if (!Object.prototype.hasOwnProperty.call(opts, key)) continue; if (key === "type" || key === "small") continue; createOpts[key] = opts[key]; }

PRA-20 Resolve/justify — Source-of-truth: Module.register failure catch lacks regression test and removal condition enforcement

  • Location: src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts:395
  • Category: correctness
  • Problem: The catch block at line 395 handles Module.register failure as a localized workaround for Node version/permission limitations. No regression test exists for this failure path, and the removal condition (Node >=20.6 guaranteed in sandbox) is not enforced.
  • Impact: Workaround persists without automated verification. Could mask real issues or be removed prematurely.
  • Recommended action: Add test for Module.register failure path (see PRA-11). Enforce minimum Node version in sandbox (see PRA-20) to make removal condition actionable.
  • Expected follow-up: Resolve in this PR or explain why the risk is acceptable.
  • Verification: Line 395: } catch (_e) { warnWhatsappQrCompact(...); } — no test, no version gate
  • Missing regression test: Add test mocking Module.register to throw; verify warnWhatsappQrCompact called. Add sandbox Node version check.
  • Done when: The risk is fixed or explicitly justified in the PR. Verification: Line 395: } catch (_e) { warnWhatsappQrCompact(...); } — no test, no version gate.
  • Evidence: installOpenClawQrTerminalSourceLoader catch block at line 395; no test covers this path

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: whatsapp-qr-compact-e2e
Optional E2E: None

Dispatch hint: whatsapp-qr-compact-e2e

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

Optional E2E

  • None.

New E2E recommendations

  • whatsapp-channel-login-sandbox-integration (medium): Existing coverage validates the real renderer boundary without a sandbox or WhatsApp account. A future sandbox-level WhatsApp channel-login dry-run/fixture test would further prove the installed OpenClaw CLI login command receives the preload through the actual sandbox startup wiring.
    • Suggested test: Add a live E2E that launches a sandbox, invokes the WhatsApp channel-login path with a mocked/stubbed WhatsApp pairing source, and asserts the compiled compact-QR preload is active in the actual openclaw channels login --channel whatsapp process.

Dispatch hint

  • Workflow: .github/workflows/regression-e2e.yaml
  • jobs input: whatsapp-qr-compact-e2e

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

E2E Target Recommendation

Required E2E targets: None
Optional E2E targets: None

Workflow run

Full E2E target advisor summary

E2E Target Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E targets

  • None. New free-standing live E2E test test/e2e/live/whatsapp-qr-compact.test.ts is not wired into .github/workflows/e2e.yaml, so the E2E target workflow cannot dispatch it yet. Add a discrete job or register it as a typed live target before treating the PR as E2E-runnable.

Optional E2E targets

  • None.

Relevant changed files

  • src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts
  • test/e2e/live/whatsapp-qr-compact.test.ts

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings

Merge posture: No blocking advisor findings
Primary next action: Add or justify PRA-T1 and any related test follow-ups.
Open items: 0 required · 0 warnings · 0 suggestions · 1 test follow-up
Since last review: 2 prior items resolved · 0 still apply · 0 new items found

Action checklist

  • PRA-T1 Add or justify test follow-up: Acceptance clause
Test follow-ups to resolve or justify

If these cover changed behavior, prefer adding them in this PR; otherwise state why existing coverage is enough or link the follow-up.

  • PRA-T1 Acceptance clause — Regression: Unknown — earlier versions not tested. — add test evidence or identify existing coverage. No regression-range claim is made in the diff. The tests focus on the bundled OpenClaw renderer and the current reporter path.

Workflow run details

This is an automated, non-binding review; it still expects maintainers and agents to respond to each required or warning item. Treat suggestions as current-PR improvements when they touch changed code; defer only with maintainer rationale or a linked follow-up. A human maintainer must make the final merge decision.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/patch-openclaw-whatsapp-qr.js`:
- Around line 1-236: The new patch script is blocked because it adds a
JavaScript source file; convert `patch-openclaw-whatsapp-qr` to TypeScript like
the other OpenClaw patch scripts. Rename the entrypoint to
`scripts/patch-openclaw-whatsapp-qr.ts`, add the needed type annotations around
the top-level helpers such as `listJsFiles`, `resolveFile`, and `processFile`,
and keep the same `AUDIT_FLAG`/`FILES` behavior. Update the Dockerfile or
invocation that launches this patcher to use `node --experimental-strip-types`
so the `.ts` script runs without introducing a new `.js` file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9a2d5724-4f76-44cf-9ef0-60869f4db29d

📥 Commits

Reviewing files that changed from the base of the PR and between 5a6f489 and 9038a96.

📒 Files selected for processing (3)
  • Dockerfile
  • scripts/patch-openclaw-whatsapp-qr.js
  • test/openclaw-whatsapp-qr-patch.test.ts

Comment thread scripts/patch-openclaw-whatsapp-qr.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
scripts/patch-openclaw-whatsapp-qr.ts (1)

28-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using fs.Dirent instead of a custom DirentLike type.

fs.readdirSync(dir, { withFileTypes: true }) already returns fs.Dirent[], which has isFile()/name. The custom DirentLike cast is redundant type duplication.

♻️ Proposed simplification
-type DirentLike = {
-  isFile(): boolean;
-  name: string;
-};
-
 function listJsFiles(dir: string): string[] {
-  return (fs.readdirSync(dir, { withFileTypes: true }) as DirentLike[])
+  return fs
+    .readdirSync(dir, { withFileTypes: true })
     .filter((entry) => entry.isFile() && entry.name.endsWith(".js"))
     .map((entry) => path.join(dir, entry.name));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/patch-openclaw-whatsapp-qr.ts` around lines 28 - 37, The listJsFiles
helper uses a redundant custom DirentLike type even though fs.readdirSync(dir, {
withFileTypes: true }) already returns fs.Dirent values with isFile() and name.
Remove the DirentLike definition and the cast in listJsFiles, and rely on the
built-in fs.Dirent type directly to simplify the code while keeping the same
filtering and path.join behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/patch-openclaw-whatsapp-qr.ts`:
- Around line 28-37: The listJsFiles helper uses a redundant custom DirentLike
type even though fs.readdirSync(dir, { withFileTypes: true }) already returns
fs.Dirent values with isFile() and name. Remove the DirentLike definition and
the cast in listJsFiles, and rely on the built-in fs.Dirent type directly to
simplify the code while keeping the same filtering and path.join behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 875e882c-bd8e-4972-a19e-0711801e7d7e

📥 Commits

Reviewing files that changed from the base of the PR and between 9038a96 and 6529cab.

📒 Files selected for processing (5)
  • Dockerfile
  • scripts/patch-openclaw-whatsapp-qr.ts
  • src/lib/sandbox/build-context.ts
  • test/openclaw-whatsapp-qr-patch.test.ts
  • test/sandbox-build-context.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • Dockerfile
  • test/openclaw-whatsapp-qr-patch.test.ts

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>

@sandl99 sandl99 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.

Messaging model already provides decoupled monkey-patch style in messaging package. Please move your patch into src/lib/messaging/channels/whatsapp/runtime and update whatsapp manifest to use runtime.openclaw.nodePreloads.
That would keep the monkey-patch away from core-buildcontext and Dockerfile

@cv cv 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.

Maintainer re-review: the exact-head advisor blocker is valid and the current patch does not yet make the QR scannable on all four edges. Compiling this preload and probing the integrity-pinned openclaw@2026.6.10 renderer produced a minimum left quiet zone of 1 module versus 4 on the right: changing COMPACT_MARGIN_MODULES from 1 to 4 leaves the upstream x=-1/y=-1 loop starts unchanged. Please fix all four quiet-zone edges and add a regression against the real pinned renderer's { small: true } call path, using compiled production output and either decode proof or explicit four-edge matrix assertions; the current generated 2x2 fixture/marker checks and default-call dimension test do not cover that path. Then run the required whatsapp-qr-compact regression E2E. Also resolve the advisor's loader-shape/integrity diagnostics and explicitly remove, opt-in gate, or justify printing the pairing-credential-derived data:image/png fallback to terminal/log output before rereview.

@cv cv 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.

Current-head rereview on d218867f3: the new hash gate, four-edge rewrite for the explicit { small: true } renderer, compiled-production E2E, and removal of the credential-derived data:image/png fallback are good improvements, but the reported WhatsApp login path is still not fixed.

I ran the pinned OpenClaw live E2E and added only the advisor-requested four-edge assertions for the real no-options renderQrTerminal(qr) call. The current head produced loginDefault = { rows: 29, cols: 55, left: 0, right: 0, top: 0, bottom: 2 }; the same run produced explicitSmall = { rows: 31, cols: 61, left: 4, right: 4, top: 4, bottom: 4 }. The login-default assertions therefore fail (0 < 4). The existing test passes because it checks only row count for loginDefault, so it masks the acceptance failure while proving the separate explicit-small path.

Please make the actual no-options login render scanner-safe (at minimum a four-module quiet zone on all four edges, or prove decoding to the original payload), keep that assertion in the compiled pinned-renderer E2E, and rerun whatsapp-qr-compact-e2e. Also finish the prior diagnostics request: loader registration/hash/preimage failure must emit a non-secret warning rather than silently skipping the patch, with a regression. Update the PR description to remove its now-stale data:image/png fallback claim.

@cv cv 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.

Approved exact head a03d53b205 after the remaining blockers were resolved. The real no-options login path now renders from qrcode.create(...).modules with 4/4/4/4 quiet zones; the compiled pinned-renderer E2E and required whatsapp-qr-compact-e2e run 28885897565 pass. Independent raster/decode validation recovered the exact 180-character payload, and qrcode version/ECL/mask options, Promise errors, and callback behavior are preserved. Loader registration/integrity/preimage failures now emit non-secret diagnostics, the data-URL fallback remains absent, the PR body is current, all 9 commits are Verified, CI is fully green, and the primary exact-head advisor is merge_as_is. Nemotron’s pre-existing @ts-nocheck, source-loaded unit seam, and hard-fail recommendation are nonblocking here: production uses compiled output, the exact compiled E2E covers the path, and a loader failure degrades only the explicit-small rewrite while the real login CJS renderer remains active and warns.

@sandl99

sandl99 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

@laitingsheng The QR data:png file was pushed back by advisor due to security, we should have a opt-in gate if we want to keep.

@cv
cv merged commit a747e56 into main Jul 7, 2026
50 checks passed
@cv
cv deleted the fix/whatsapp-pairing-qr-scannable branch July 7, 2026 17:44
apurvvkumaria added a commit that referenced this pull request Jul 8, 2026
<!-- markdownlint-disable MD041 -->
## Summary

Prepares the user-facing documentation for NemoClaw v0.0.76 and closes
the release-prep documentation gate. It adds the release highlights,
documents the arm64 Local NIM warning and expanded image cleanup
behavior, and fixes agent-specific command headings in generated guides.

## Changes

- Add the v0.0.76 release-notes section and move the shared-gateway
route containment entry out of the v0.0.74 history where it was
incorrectly placed.
- Document the advisory Linux arm64 Local NIM manifest warning in the
canonical platform matrix and local-inference guidance.
- Document that `gc` scans both gateway-built and locally prebuilt
sandbox image repositories.
- Keep OpenClaw and Hermes session headings out of the generated Deep
Agents command guide.
- Add a focused variant regression test for the agent-specific session
headings.

### Source summary

| Merged sources | Documentation coverage |
| --- | --- |
| [#6414](#6414),
[#6418](#6418),
[#6416](#6416),
[#6344](#6344) | v0.0.76 release
notes and the Deep Agents quickstart/inference routes |
| [#6340](#6340) | v0.0.76
release notes and existing Deep Agents observability guidance |
| [#6338](#6338),
[#6378](#6378),
[#6297](#6297) | v0.0.76 release
notes and existing inference/troubleshooting guidance |
| [#6362](#6362) | v0.0.76
release notes and existing lifecycle, command, and credential guidance |
| [#6330](#6330),
[#6307](#6307),
[#6008](#6008) | v0.0.76 release
notes and existing security, troubleshooting, and command guidance |
| [#6382](#6382) | v0.0.76
release notes and existing MCP/command guidance |
| [#6326](#6326),
[#5868](#5868),
[#5539](#5539) | v0.0.76 release
notes, platform matrix, inference options, and local-inference guidance
|
| [#6396](#6396),
[#6390](#6390),
[#6007](#6007) | v0.0.76 release
notes and existing messaging guidance |
| [#5388](#5388),
[#6249](#6249),
[#6303](#6303),
[#6306](#6306) | v0.0.76 release
notes and command/lifecycle guidance |

## Type of Change

- [ ] Code change (feature, bug fix, or refactor)
- [x] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Quality Gates

- [x] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [ ] Tests not applicable — justification:
- [x] Docs updated for user-facing behavior changes
- [ ] Docs not applicable — justification:
- [ ] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [ ] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification:
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Verification

- [x] PR description includes the DCO sign-off declaration and every
commit appears as `Verified` in GitHub
- [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run check:diff` passed when hooks were skipped or unavailable
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — `npx vitest run --project integration
test/generate-platform-docs.test.ts test/agent-variant-docs.test.ts
test/sync-agent-variant-docs.test.ts` (3 files, 29 tests passed)
- [ ] Applicable broad gate passed — `npm test` for broad
runtime/test-harness changes; `npm run check` for repo-wide
validation/coverage changes — command/result:
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, or credentials committed
- [ ] `npm run docs` builds without warnings (doc changes only) —
completed with 0 errors and 2 pre-existing Fern warnings
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Added v0.0.76 release notes content, and removed an older conflicting
bullet from the surrounding release history.
* Expanded Local NVIDIA NIM guidance across inference/provider docs,
including an advisory for Linux arm64 DGX Spark/DGX Station hosts when a
matching `linux/arm64` image manifest is unavailable.
* Updated the command reference for correct session-section rendering
and clarified `gc` image cleanup sources.
* **Tests**
* Added coverage ensuring Deep Agents omits sessions headings while
Hermes includes them.
* **CI**
* Refreshed Local NVIDIA NIM provider notes used in the platform matrix.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
## Summary

The bundled OpenClaw/WhatsApp login path renders `openclaw channels
login --channel whatsapp` through `renderQrTerminal(qr)` with no `{
small }` option. The previous compacting path made the QR fit in the
terminal, but the real no-options login render still used `qrcode`'s
built-in compact terminal output with too little quiet zone for reliable
scanning.

This PR installs a WhatsApp runtime preload that renders terminal QR
output from `qrcode.create(...).modules` with a four-module quiet zone
on all edges, and also hash-gates the pinned OpenClaw 2026.6.10 explicit
`{ small: true }` compact renderer rewrite. The preload does not print a
credential-derived `data:image/png` fallback URL.

## Related Issue

Fixes NVIDIA#6386

## Changes

- Move the WhatsApp QR fix into
`src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.ts` and
wire it through the manifest runtime preload path.
- Render the real no-options WhatsApp login QR path with NemoClaw's
four-module compact terminal renderer instead of `qrcode`'s built-in
small terminal renderer.
- Keep the explicit OpenClaw `{ small: true }` compact renderer source
rewrite for the pinned reviewed renderer, including all four quiet-zone
edges.
- Emit non-secret warnings when the ESM source-loader registration,
integrity check, or preimage match fails instead of silently skipping
the source rewrite.
- Keep non-terminal QR outputs unchanged and do not print raw
`data:image/png` fallback data to terminal/log output.

## Type of Change

- [x] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Quality Gates

- [x] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [ ] Tests not applicable — justification:
- [ ] Docs updated for user-facing behavior changes
- [x] Docs not applicable — justification: no new command, flag, or
config surface; the change restores the already-documented in-sandbox
WhatsApp pairing flow to a scannable terminal QR.
- [x] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [ ] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification: awaiting maintainer
review (messaging pairing flow + runtime preload).
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Verification

- [x] PR description includes the DCO sign-off declaration and every
commit appears as `Verified` in GitHub
- [ ] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run check:diff` passed when hooks were skipped or unavailable
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — command/result or justification:
- `./node_modules/.bin/vitest run --project cli
src/lib/messaging/channels/whatsapp/runtime/whatsapp-qr-compact.test.ts`
-> 23/23 passed
- `./node_modules/.bin/vitest run --project integration
test/whatsapp-qr-compact.test.ts` -> 23/23 passed
- `NEMOCLAW_RUN_LIVE_E2E=1 ./node_modules/.bin/vitest run --project
e2e-live test/e2e/live/whatsapp-qr-compact.test.ts --silent=false
--reporter=default` -> 1/1 passed
  - `npm run test-conditionals:scan -- --top 25` -> passed
  - `npm run typecheck:cli` -> passed
- [ ] Applicable broad gate passed — `npm test` for broad
runtime/test-harness changes; `npm run check` for repo-wide
validation/coverage changes — command/result:
- [x] No secrets, API keys, or credentials committed
- [ ] `npm run docs` builds without warnings (doc changes only)
- [ ] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
Signed-off-by: Tinson Lai <tinsonl@nvidia.com>

---------

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
Co-authored-by: San Dang <sdang@nvidia.com>
Hadar301 pushed a commit to Hadar301/NemoClaw-OpenShift that referenced this pull request Jul 12, 2026
<!-- markdownlint-disable MD041 -->
## Summary

Prepares the user-facing documentation for NemoClaw v0.0.76 and closes
the release-prep documentation gate. It adds the release highlights,
documents the arm64 Local NIM warning and expanded image cleanup
behavior, and fixes agent-specific command headings in generated guides.

## Changes

- Add the v0.0.76 release-notes section and move the shared-gateway
route containment entry out of the v0.0.74 history where it was
incorrectly placed.
- Document the advisory Linux arm64 Local NIM manifest warning in the
canonical platform matrix and local-inference guidance.
- Document that `gc` scans both gateway-built and locally prebuilt
sandbox image repositories.
- Keep OpenClaw and Hermes session headings out of the generated Deep
Agents command guide.
- Add a focused variant regression test for the agent-specific session
headings.

### Source summary

| Merged sources | Documentation coverage |
| --- | --- |
| [NVIDIA#6414](NVIDIA#6414),
[NVIDIA#6418](NVIDIA#6418),
[NVIDIA#6416](NVIDIA#6416),
[NVIDIA#6344](NVIDIA#6344) | v0.0.76 release
notes and the Deep Agents quickstart/inference routes |
| [NVIDIA#6340](NVIDIA#6340) | v0.0.76
release notes and existing Deep Agents observability guidance |
| [NVIDIA#6338](NVIDIA#6338),
[NVIDIA#6378](NVIDIA#6378),
[NVIDIA#6297](NVIDIA#6297) | v0.0.76 release
notes and existing inference/troubleshooting guidance |
| [NVIDIA#6362](NVIDIA#6362) | v0.0.76
release notes and existing lifecycle, command, and credential guidance |
| [NVIDIA#6330](NVIDIA#6330),
[NVIDIA#6307](NVIDIA#6307),
[NVIDIA#6008](NVIDIA#6008) | v0.0.76 release
notes and existing security, troubleshooting, and command guidance |
| [NVIDIA#6382](NVIDIA#6382) | v0.0.76
release notes and existing MCP/command guidance |
| [NVIDIA#6326](NVIDIA#6326),
[NVIDIA#5868](NVIDIA#5868),
[NVIDIA#5539](NVIDIA#5539) | v0.0.76 release
notes, platform matrix, inference options, and local-inference guidance
|
| [NVIDIA#6396](NVIDIA#6396),
[NVIDIA#6390](NVIDIA#6390),
[NVIDIA#6007](NVIDIA#6007) | v0.0.76 release
notes and existing messaging guidance |
| [NVIDIA#5388](NVIDIA#5388),
[NVIDIA#6249](NVIDIA#6249),
[NVIDIA#6303](NVIDIA#6303),
[NVIDIA#6306](NVIDIA#6306) | v0.0.76 release
notes and command/lifecycle guidance |

## Type of Change

- [ ] Code change (feature, bug fix, or refactor)
- [x] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Quality Gates

- [x] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [ ] Tests not applicable — justification:
- [x] Docs updated for user-facing behavior changes
- [ ] Docs not applicable — justification:
- [ ] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [ ] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification:
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Verification

- [x] PR description includes the DCO sign-off declaration and every
commit appears as `Verified` in GitHub
- [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run check:diff` passed when hooks were skipped or unavailable
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — `npx vitest run --project integration
test/generate-platform-docs.test.ts test/agent-variant-docs.test.ts
test/sync-agent-variant-docs.test.ts` (3 files, 29 tests passed)
- [ ] Applicable broad gate passed — `npm test` for broad
runtime/test-harness changes; `npm run check` for repo-wide
validation/coverage changes — command/result:
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, or credentials committed
- [ ] `npm run docs` builds without warnings (doc changes only) —
completed with 0 errors and 2 pre-existing Fern warnings
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Added v0.0.76 release notes content, and removed an older conflicting
bullet from the surrounding release history.
* Expanded Local NVIDIA NIM guidance across inference/provider docs,
including an advisory for Linux arm64 DGX Spark/DGX Station hosts when a
matching `linux/arm64` image manifest is unavailable.
* Updated the command reference for correct session-section rendering
and clarified `gc` image cleanup sources.
* **Tests**
* Added coverage ensuring Deep Agents omits sessions headings while
Hermes includes them.
* **CI**
* Refreshed Local NVIDIA NIM provider notes used in the platform matrix.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery bug-fix PR fixes a bug or regression integration: whatsapp WhatsApp integration or channel behavior NV QA Bugs found by the NVIDIA QA Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Ubuntu 24.04][Agent&Skills] openclaw channels login --channel whatsapp renders QR code that cannot be scanned by WhatsApp or any QR reader

4 participants