fix(whatsapp): render compact pairing QR and split gateway diagnostics#4607
Conversation
In-sandbox WhatsApp pairing delegated QR rendering to the upstream
@openclaw/whatsapp plugin, which draws the Linked-Devices QR at full size
via qrcode-terminal. On DGX Spark terminals the code filled 50-80+ rows and
hundreds of columns, so it could not be captured in one phone-camera frame.
The reported "1008 abnormal closure" then surfaced as raw gateway text with
no separation from the QR problem.
NemoClaw owns the user-facing pairing workflow, so wrap the supported path:
- Add a NemoClaw-owned preload (whatsapp-qr-compact.js) that forces
qrcode-terminal into the same `{ small: true }` half-block mode the
host-side WeChat QR path already uses, quartering the rendered area
without changing the payload. It hooks Module._load so the plugin's
nested copy is patched, and is idempotent.
- Install it only when WhatsApp is configured and inject it for the single
`openclaw channels login --channel whatsapp` invocation via the sandbox
guard — the gateway never renders the QR, so it stays off global
NODE_OPTIONS. Validate it under validate_tmp_permissions (root:444).
- Validate OPENCLAW_GATEWAY_URL before pairing and, on a non-zero login
exit, print retry/diagnostic guidance that distinguishes a gateway close
(e.g. 1008) from a QR-size issue.
Add a unit test for the renderer (forces small, bounds lines, idempotent)
and the guard branch (gateway preflight, preload injection, close
diagnostics), plus a live e2e check that the preload + guard wiring land in
the sandbox. Carries a documented removal criterion for when upstream
renders a scan-friendly QR by default.
Fixes NVIDIA#4522
Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds a Node.js preload to force compact WhatsApp QR rendering, installs and optionally injects the preload into the openclaw login guard, updates sandbox help text, and adds unit/e2e tests validating rendering, idempotency, and guard wiring. ChangesWhatsApp Compact QR Pairing
Sequence DiagramsequenceDiagram
participant Startup as NemoClaw Startup
participant InstallFunc as install_whatsapp_qr_compact()
participant TmpPreload as /tmp/nemoclaw-whatsapp-qr-compact.js
participant OpenClawGuard as openclaw channels login guard
participant NodeRuntime as spawned openclaw process (NODE_OPTIONS)
participant QRLibrary as qrcode-terminal
Startup->>InstallFunc: call during service init
InstallFunc->>TmpPreload: create preload file in /tmp
OpenClawGuard->>OpenClawGuard: validate OPENCLAW_GATEWAY_URL
OpenClawGuard->>NodeRuntime: set NODE_OPTIONS --require /tmp/...
NodeRuntime->>QRLibrary: require('qrcode-terminal') (preload intercepts)
QRLibrary->>NodeRuntime: generate() enforced with small:true
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 `@test/e2e/test-messaging-providers.sh`:
- Around line 919-924: The current check uses whatsapp_qr_guard_wiring and only
greps for 'nemoclaw-whatsapp-qr-compact.js' which can miss whether NODE_OPTIONS
actually injects it; update the assertion to grep for the exact require flag
pattern (e.g., "--require $_whatsapp_qr_compact" or the resolved value of
_whatsapp_qr_compact) via sandbox_exec so the test verifies the actual --require
injection string, adjust the variable used (whatsapp_qr_guard_wiring) and the
subsequent conditional message to reflect the check for the --require injection
rather than just the filename.
🪄 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: c228f084-608a-4055-b508-f89bfb45772f
📒 Files selected for processing (5)
nemoclaw-blueprint/scripts/whatsapp-qr-compact.jsscripts/nemoclaw-start.shsrc/lib/sandbox/channels.tstest/e2e/test-messaging-providers.shtest/whatsapp-qr-compact.test.ts
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
CodeRabbit flagged that the M-WA6c guard-wiring check grepped only for the preload filename, which also appears in the install banner and the literal path assignment — so it would pass even if the NODE_OPTIONS `--require` injection regressed. Grep the emitted proxy-env file for the literal `--require $_whatsapp_qr_compact` token so the test pins the actual injection. Signed-off-by: Yimo Jiang <yimoj@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
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/nemoclaw-start.sh`:
- Around line 1991-1999: The current guard only checks OPENCLAW_GATEWAY_URL is
non-empty; add a validation step before calling openclaw (where openclaw
channels login is used) to reject malformed or local gateway URLs. After the
existing empty check, validate OPENCLAW_GATEWAY_URL with a shell-safe regex
(e.g. require a scheme like http:// or https:// and a non-local host), and also
reject localhost/127.0.0.1 variants; on failure print a clear error (same style
as existing messages) and return 1 so openclaw is never invoked with a bad URL.
🪄 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: ef067405-7d5d-4b8e-bfbb-9a9ee7e3ad0e
📒 Files selected for processing (5)
nemoclaw-blueprint/scripts/whatsapp-qr-compact.jsscripts/nemoclaw-start.shsrc/lib/sandbox/channels.tstest/e2e/test-messaging-providers.shtest/whatsapp-qr-compact.test.ts
CodeRabbit noted the gateway preflight only checked OPENCLAW_GATEWAY_URL for non-emptiness, so a clobbered value (e.g. `foo` or an `http://` typo) would still reach `openclaw channels login` and fail inside the login as the same ambiguous close this guard exists to separate from QR rendering. Validate the scheme up front: the OpenClaw gateway is a WebSocket endpoint (set to ws://127.0.0.1:<port> at boot), so accept only ws:// and wss:// and reject anything else with a gateway/env diagnostic. (CodeRabbit's literal suggestion to require http(s):// and reject 127.0.0.1 would have rejected the real local ws:// gateway URL, so the scheme check is adapted to this stack.) Add parametrized guard tests covering rejected and accepted schemes. Signed-off-by: Yimo Jiang <yimoj@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary - Adds the v0.0.56 release notes section with links to the deeper docs pages for installer, status, inference, messaging, policy, and lifecycle changes. - Updates source docs for the remaining release-prep gaps around `uv` in the PyPI preset, compact WhatsApp pairing guidance, and `nemoclaw inference set` command boundaries. - Refreshes generated `nemoclaw-user-*` skills and removes skipped experimental command terms from generated skill surfaces. ## Source summary - #4613 -> `docs/manage-sandboxes/lifecycle.mdx`, `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Documents that public installs and `nemoclaw update` follow the maintained `lkg` tag by default. - #4419 -> `docs/about/release-notes.mdx`: Notes that non-interactive Linux installs can reactivate Docker group membership and continue in one installer run when `sg docker` is available. - #4550 -> `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Captures live sandbox agent-version probing for status, connect, and upgrade checks. - #4609 -> `docs/inference/use-local-inference.mdx`, `docs/about/release-notes.mdx`: Captures the GPU Docker-driver host-network local-inference reachability gate. - #4607 -> `docs/manage-sandboxes/messaging-channels.mdx`, `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Documents compact WhatsApp QR pairing guidance and gateway/session diagnostics. - #4582 -> `docs/manage-sandboxes/messaging-channels.mdx`, `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Reflects Slack credential validation before enabling the channel. - #4554 -> `docs/manage-sandboxes/messaging-channels.mdx`, `docs/reference/troubleshooting.mdx`, `docs/about/release-notes.mdx`: Keeps Telegram allowlist alias guidance in the generated user skills and release notes. - #4563 -> `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Includes the new `nemoclaw <name> skill remove <skill>` command in command docs and release notes. - #4566 -> `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Documents the `nemoclaw inference set` redirect boundary when `--provider` or `--model` is missing. - #4323 -> `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Captures per-sandbox status JSON support. - #4506 -> `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Captures debug command sandbox-name validation and safer tarball writing. - #4569 -> `docs/network-policy/integration-policy-examples.mdx`, `docs/about/release-notes.mdx`: Documents that the `pypi` preset allows `/usr/local/bin/uv`. - #4579 -> `docs/network-policy/integration-policy-examples.mdx`, `docs/about/release-notes.mdx`: Captures observable Jira preset validation guidance. - #4229 -> `docs/manage-sandboxes/lifecycle.mdx`, `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Documents user-data preservation defaults for uninstall. - #4399 -> `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Captures CPU-only sandbox intent preservation across rebuilds. - #4058 -> `docs/reference/commands.mdx`, `docs/about/release-notes.mdx`: Captures safer snapshot restore behavior around existing destinations. - #4155 and #4460 -> skipped by `docs/.docs-skip`: Removed skipped experimental command terms from source docs and generated skill evals instead of documenting those features. ## Verification - `python3 scripts/docs-to-skills.py docs/ .agents/skills/ --prefix nemoclaw-user --doc-platform fern-mdx` - `npm run docs` (passes; Fern reports the pre-existing light-mode accent contrast warning) - `rg "permissive mode|shields down|shields up|shields status|config rotate-token|rotate-token" .agents/skills` (no matches) - `npm run build:cli` (run to refresh local CLI artifacts for the pre-push TypeScript hook) - Commit hooks passed, including `NEMOCLAW_* env-var documentation gate`, `Verify docs-to-skills output`, `markdownlint-cli2`, `gitleaks`, and `Test (skills YAML)`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Expanded Model Router setup with YAML examples, flow diagrams, and credential handling; strengthened agent-config immutability and integrity guidance; messaging channels updated (Telegram aliases, WhatsApp pairing/diagnostics); CLI docs revised (GPU detection, inference set behavior, uninstall/rebuild preservation); overview rebranded to NemoClaw and added v0.0.56 release notes. * **New Features** * Added `nemoclaw <name> channels status` (messaging diagnostics, JSON); added `nemoclaw <name> skill remove`; Hermes no longer marked experimental; DGX Spark quickstart sandbox-name note. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary - Add the missing `v0.0.57` release-notes section with links to the detailed docs pages for command, inference, onboarding, messaging, status, installer, and policy changes. - Remove public references to docs-skip terms from source docs and regenerate the NemoClaw user skills from the current Fern MDX docs. - Carry forward generated references for the per-agent documentation split, including Hermes-specific reference files. ## Source summary - #4615 and #4653 -> `docs/about/release-notes.mdx`, `docs/reference/commands.mdx`: Release notes now cover host-side `sessions` and `agents` commands plus `NEMOCLAW_EXTRA_AGENTS_JSON` secondary-agent baking. - #4163, #4204, #4611, #4619, and #4676 -> `docs/about/release-notes.mdx`, `docs/inference/use-local-inference.mdx`: Release notes now cover managed vLLM progress/readiness, DGX Spark model default changes, local Ollama streaming usage, and inference route divergence warnings. - #4267, #4601, #4609, #4642, #4645, and #4661 -> `docs/about/release-notes.mdx`, `docs/reference/commands.mdx`: Release notes now cover UFW auto-remediation, local-inference reachability gates, gateway reuse/binding, cancel rollback, and policy selection persistence. - #4577, #4582, #4607, and #4660 -> `docs/about/release-notes.mdx`, `docs/manage-sandboxes/messaging-channels.mdx`: Release notes now cover Slack validation, atomic `channels add`, WhatsApp QR diagnostics, and Slack placeholder normalization. - #4388, #4600, #4646, and #4647 -> `docs/about/release-notes.mdx`, `docs/reference/commands.mdx`: Release notes now cover status failure layers, paused-container hints, Docker-driver doctor behavior, and non-destructive stale-registry recovery. - #4569, #4579, and #4678 -> `docs/about/release-notes.mdx`, `docs/manage-sandboxes/lifecycle.mdx`, `docs/network-policy/integration-policy-examples.mdx`: Release notes now cover installer tag pinning, PyPI `uv` policy access, and observable Jira validation. - #4632 -> `.agents/skills/`: Regenerated user skills from the current per-agent docs source, including newly generated Hermes reference files. ## Verification - `python3 scripts/docs-to-skills.py docs/ .agents/skills/ --prefix nemoclaw-user --doc-platform fern-mdx` - `rg "permissive mode|shields down|shields up|shields status|config rotate-token|rotate-token" docs --glob "*.mdx"` - `rg "permissive mode|shields down|shields up|shields status|config rotate-token|rotate-token" .agents/skills --glob "*.md"` - `npm run docs` - `npm run build:cli` - Commit hooks: markdownlint, docs-to-skills verification, gitleaks, skills YAML, commitlint <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Restructured documentation to clearly distinguish OpenClaw and Hermes agent variants throughout user guides. * Enhanced security, credential storage, and deployment guidance with clearer setup flows. * Added Hermes plugin installation and ecosystem documentation. * Improved workspace, messaging, and policy management references with variant-specific command examples. * Refined troubleshooting and CLI reference sections for clarity. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…VIDIA#4522) The in-sandbox `openclaw channels login --channel whatsapp` pairing QR still rendered full size (~56 rows) and overflowed the terminal after PR NVIDIA#4607. Root cause: the upstream @openclaw/whatsapp plugin renders the QR via `renderQrTerminal()` → the `qrcode` package's `toString(text, { type: "terminal", small })`, and the bundled plugin (version-matched to OpenClaw, e.g. 2026.5.27) passes NO `small` flag, so it defaults to full size. The previous fix patched the unrelated `qrcode-terminal` package, which the WhatsApp plugin never loads — so the compact rendering was never applied at the actual pairing entrypoint. Fix: - Rewrite the NODE_OPTIONS preload to patch the `qrcode` package's `toString` (the real renderer) and force `small: true` for terminal renders, independent of what the plugin version passes. Detection requires an OWN `toString` + `create` so internal qrcode submodules are not mutated. The qrcode-terminal `generate` path is still patched as a fallback. Non-terminal renders (svg/png/utf8) are untouched. - Wire the preload into the connect-session NODE_OPTIONS (deferred `[ -f ]` guard) so ANY openclaw invocation in the session renders compact, not just the bypassable openclaw() shell-function path; the guard injection remains as defense-in-depth. Coverage (proves rendered QR size, not just preload presence): - New hermetic E2E `test/e2e/test-whatsapp-qr-compact-e2e.sh` installs the exact @openclaw/whatsapp + openclaw versions pinned in Dockerfile.base and drives the real `renderQrTerminal` symbol the channel-login onQr callback uses: 56 rows without the preload, 29 rows with it. Wired into regression-e2e.yaml. - Sandbox parity (test-messaging-providers.sh M-WA6d) renders the QR in-sandbox through the baked renderer with the connect-session NODE_OPTIONS active and asserts compact dimensions. - Unit test rewritten to patch/verify the `qrcode` package shape and the connect-session wiring. Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
…VIDIA#4522) The in-sandbox `openclaw channels login --channel whatsapp` pairing QR still rendered full size (~56 rows) and overflowed the terminal after PR NVIDIA#4607. Root cause: the upstream @openclaw/whatsapp plugin renders the QR via `renderQrTerminal()` → the `qrcode` package's `toString(text, { type: "terminal", small })`, and the bundled plugin (version-matched to OpenClaw, e.g. 2026.5.27) passes NO `small` flag, so it defaults to full size. The previous fix patched the unrelated `qrcode-terminal` package, which the WhatsApp plugin never loads — so the compact rendering was never applied at the actual pairing entrypoint. Fix: - Rewrite the NODE_OPTIONS preload to patch the `qrcode` package's `toString` (the real renderer) and force `small: true` for terminal renders, independent of what the plugin version passes. Detection requires an OWN `toString` + `create` so internal qrcode submodules are not mutated. The qrcode-terminal `generate` path is still patched as a fallback. Non-terminal renders (svg/png/utf8) are untouched. - Wire the preload into the connect-session NODE_OPTIONS (deferred `[ -f ]` guard) so ANY openclaw invocation in the session renders compact, not just the bypassable openclaw() shell-function path; the guard injection remains as defense-in-depth. Coverage (proves rendered QR size, not just preload presence): - New hermetic E2E `test/e2e/test-whatsapp-qr-compact-e2e.sh` installs the exact @openclaw/whatsapp + openclaw versions pinned in Dockerfile.base and drives the real `renderQrTerminal` symbol the channel-login onQr callback uses: 56 rows without the preload, 29 rows with it. Wired into regression-e2e.yaml. - Sandbox parity (test-messaging-providers.sh M-WA6d) renders the QR in-sandbox through the baked renderer with the connect-session NODE_OPTIONS active and asserts compact dimensions. - Unit test rewritten to patch/verify the `qrcode` package shape and the connect-session wiring. Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
…VIDIA#4522) The in-sandbox `openclaw channels login --channel whatsapp` pairing QR still rendered full size (~56 rows) and overflowed the terminal after PR NVIDIA#4607. Root cause: the upstream @openclaw/whatsapp plugin renders the QR via `renderQrTerminal()` → the `qrcode` package's `toString(text, { type: "terminal", small })`, and the bundled plugin (version-matched to OpenClaw, e.g. 2026.5.27) passes NO `small` flag, so it defaults to full size. The previous fix patched the unrelated `qrcode-terminal` package, which the WhatsApp plugin never loads — so the compact rendering was never applied at the actual pairing entrypoint. Fix: - Rewrite the NODE_OPTIONS preload to patch the `qrcode` package's `toString` (the real renderer) and force `small: true` for terminal renders, independent of what the plugin version passes. Detection requires an OWN `toString` + `create` so internal qrcode submodules are not mutated. The qrcode-terminal `generate` path is still patched as a fallback. Non-terminal renders (svg/png/utf8) are untouched. - Wire the preload into the connect-session NODE_OPTIONS (deferred `[ -f ]` guard) so ANY openclaw invocation in the session renders compact, not just the bypassable openclaw() shell-function path; the guard injection remains as defense-in-depth. Coverage (proves rendered QR size, not just preload presence): - New hermetic E2E `test/e2e/test-whatsapp-qr-compact-e2e.sh` installs the exact @openclaw/whatsapp + openclaw versions pinned in Dockerfile.base and drives the real `renderQrTerminal` symbol the channel-login onQr callback uses: 56 rows without the preload, 29 rows with it. Wired into regression-e2e.yaml. - Sandbox parity (test-messaging-providers.sh M-WA6d) renders the QR in-sandbox through the baked renderer with the connect-session NODE_OPTIONS active and asserts compact dimensions. - Unit test rewritten to patch/verify the `qrcode` package shape and the connect-session wiring. Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
…VIDIA#4522) The in-sandbox `openclaw channels login --channel whatsapp` pairing QR still rendered full size (~56 rows) and overflowed the terminal after PR NVIDIA#4607. Root cause: the upstream @openclaw/whatsapp plugin renders the QR via `renderQrTerminal()` → the `qrcode` package's `toString(text, { type: "terminal", small })`, and the bundled plugin (version-matched to OpenClaw, e.g. 2026.5.27) passes NO `small` flag, so it defaults to full size. The previous fix patched the unrelated `qrcode-terminal` package, which the WhatsApp plugin never loads — so the compact rendering was never applied at the actual pairing entrypoint. Fix: - Rewrite the NODE_OPTIONS preload to patch the `qrcode` package's `toString` (the real renderer) and force `small: true` for terminal renders, independent of what the plugin version passes. Detection requires an OWN `toString` + `create` so internal qrcode submodules are not mutated. The qrcode-terminal `generate` path is still patched as a fallback. Non-terminal renders (svg/png/utf8) are untouched. - Wire the preload into the connect-session NODE_OPTIONS (deferred `[ -f ]` guard) so ANY openclaw invocation in the session renders compact, not just the bypassable openclaw() shell-function path; the guard injection remains as defense-in-depth. Coverage (proves rendered QR size, not just preload presence): - New hermetic E2E `test/e2e/test-whatsapp-qr-compact-e2e.sh` installs the exact @openclaw/whatsapp + openclaw versions pinned in Dockerfile.base and drives the real `renderQrTerminal` symbol the channel-login onQr callback uses: 56 rows without the preload, 29 rows with it. Wired into regression-e2e.yaml. - Sandbox parity (test-messaging-providers.sh M-WA6d) renders the QR in-sandbox through the baked renderer with the connect-session NODE_OPTIONS active and asserts compact dimensions. - Unit test rewritten to patch/verify the `qrcode` package shape and the connect-session wiring. Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
…VIDIA#4522) The in-sandbox `openclaw channels login --channel whatsapp` pairing QR still rendered full size (~56 rows) and overflowed the terminal after PR NVIDIA#4607. Root cause: the upstream @openclaw/whatsapp plugin renders the QR via `renderQrTerminal()` → the `qrcode` package's `toString(text, { type: "terminal", small })`, and the bundled plugin (version-matched to OpenClaw, e.g. 2026.5.27) passes NO `small` flag, so it defaults to full size. The previous fix patched the unrelated `qrcode-terminal` package, which the WhatsApp plugin never loads — so the compact rendering was never applied at the actual pairing entrypoint. Fix: - Rewrite the NODE_OPTIONS preload to patch the `qrcode` package's `toString` (the real renderer) and force `small: true` for terminal renders, independent of what the plugin version passes. Detection requires an OWN `toString` + `create` so internal qrcode submodules are not mutated. The qrcode-terminal `generate` path is still patched as a fallback. Non-terminal renders (svg/png/utf8) are untouched. - Wire the preload into the connect-session NODE_OPTIONS (deferred `[ -f ]` guard) so ANY openclaw invocation in the session renders compact, not just the bypassable openclaw() shell-function path; the guard injection remains as defense-in-depth. Coverage (proves rendered QR size, not just preload presence): - New hermetic E2E `test/e2e/test-whatsapp-qr-compact-e2e.sh` installs the exact @openclaw/whatsapp + openclaw versions pinned in Dockerfile.base and drives the real `renderQrTerminal` symbol the channel-login onQr callback uses: 56 rows without the preload, 29 rows with it. Wired into regression-e2e.yaml. - Sandbox parity (test-messaging-providers.sh M-WA6d) renders the QR in-sandbox through the baked renderer with the connect-session NODE_OPTIONS active and asserts compact dimensions. - Unit test rewritten to patch/verify the `qrcode` package shape and the connect-session wiring. Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
…VIDIA#4522) The in-sandbox `openclaw channels login --channel whatsapp` pairing QR still rendered full size (~56 rows) and overflowed the terminal after PR NVIDIA#4607. Root cause: the upstream @openclaw/whatsapp plugin renders the QR via `renderQrTerminal()` → the `qrcode` package's `toString(text, { type: "terminal", small })`, and the bundled plugin (version-matched to OpenClaw, e.g. 2026.5.27) passes NO `small` flag, so it defaults to full size. The previous fix patched the unrelated `qrcode-terminal` package, which the WhatsApp plugin never loads — so the compact rendering was never applied at the actual pairing entrypoint. Fix: - Rewrite the NODE_OPTIONS preload to patch the `qrcode` package's `toString` (the real renderer) and force `small: true` for terminal renders, independent of what the plugin version passes. Detection requires an OWN `toString` + `create` so internal qrcode submodules are not mutated. The qrcode-terminal `generate` path is still patched as a fallback. Non-terminal renders (svg/png/utf8) are untouched. - Wire the preload into the connect-session NODE_OPTIONS (deferred `[ -f ]` guard) so ANY openclaw invocation in the session renders compact, not just the bypassable openclaw() shell-function path; the guard injection remains as defense-in-depth. Coverage (proves rendered QR size, not just preload presence): - New hermetic E2E `test/e2e/test-whatsapp-qr-compact-e2e.sh` installs the exact @openclaw/whatsapp + openclaw versions pinned in Dockerfile.base and drives the real `renderQrTerminal` symbol the channel-login onQr callback uses: 56 rows without the preload, 29 rows with it. Wired into regression-e2e.yaml. - Sandbox parity (test-messaging-providers.sh M-WA6d) renders the QR in-sandbox through the baked renderer with the connect-session NODE_OPTIONS active and asserts compact dimensions. - Unit test rewritten to patch/verify the `qrcode` package shape and the connect-session wiring. Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
…VIDIA#4522) The in-sandbox `openclaw channels login --channel whatsapp` pairing QR still rendered full size (~56 rows) and overflowed the terminal after PR NVIDIA#4607. Root cause: the upstream @openclaw/whatsapp plugin renders the QR via `renderQrTerminal()` → the `qrcode` package's `toString(text, { type: "terminal", small })`, and the bundled plugin (version-matched to OpenClaw, e.g. 2026.5.27) passes NO `small` flag, so it defaults to full size. The previous fix patched the unrelated `qrcode-terminal` package, which the WhatsApp plugin never loads — so the compact rendering was never applied at the actual pairing entrypoint. Fix: - Rewrite the NODE_OPTIONS preload to patch the `qrcode` package's `toString` (the real renderer) and force `small: true` for terminal renders, independent of what the plugin version passes. Detection requires an OWN `toString` + `create` so internal qrcode submodules are not mutated. The qrcode-terminal `generate` path is still patched as a fallback. Non-terminal renders (svg/png/utf8) are untouched. - Wire the preload into the connect-session NODE_OPTIONS (deferred `[ -f ]` guard) so ANY openclaw invocation in the session renders compact, not just the bypassable openclaw() shell-function path; the guard injection remains as defense-in-depth. Coverage (proves rendered QR size, not just preload presence): - New hermetic E2E `test/e2e/test-whatsapp-qr-compact-e2e.sh` installs the exact @openclaw/whatsapp + openclaw versions pinned in Dockerfile.base and drives the real `renderQrTerminal` symbol the channel-login onQr callback uses: 56 rows without the preload, 29 rows with it. Wired into regression-e2e.yaml. - Sandbox parity (test-messaging-providers.sh M-WA6d) renders the QR in-sandbox through the baked renderer with the connect-session NODE_OPTIONS active and asserts compact dimensions. - Unit test rewritten to patch/verify the `qrcode` package shape and the connect-session wiring. Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
…4522) (#5016) ## Summary The in-sandbox `openclaw channels login --channel whatsapp` pairing QR still rendered full size (~56 rows) and overflowed the terminal after #4607, making it impossible to scan. This forces compact rendering at the *actual* renderer the pairing flow uses, with an E2E that proves the rendered QR dimensions. ## Related Issue Fixes #4522 ## Changes - **Root cause:** the WhatsApp plugin renders the pairing QR via `renderQrTerminal()` → the **`qrcode`** package's `toString(text, { type: "terminal", small })`, and the bundled `@openclaw/whatsapp` (version-matched to OpenClaw, e.g. `2026.5.27`) passes **no `small` flag**, so it defaults to full size. The previous fix patched the unrelated `qrcode-terminal` package, which the WhatsApp plugin never loads — so compact rendering was never applied at the pairing entrypoint. - **`nemoclaw-blueprint/scripts/whatsapp-qr-compact.js`:** rewrite the NODE_OPTIONS preload to patch the `qrcode` package's `toString` (the real renderer) and force `small: true` for terminal renders, independent of what the plugin version passes. Detection requires an *own* `toString` + `create`, so internal qrcode submodules are not mutated; the `qrcode-terminal` `generate` path is still patched as a fallback. Non-terminal renders (svg/png/utf8 data URIs) are untouched. - **`scripts/nemoclaw-start.sh`:** wire the preload into the connect-session `NODE_OPTIONS` (deferred `[ -f ]` guard) so *any* openclaw invocation in the session renders compact — not only the bypassable `openclaw()` shell-function path. The guard injection remains as defense-in-depth. - **`test/e2e/test-whatsapp-qr-compact-e2e.sh` (new):** hermetic reporter-workflow E2E that installs the exact `@openclaw/whatsapp` + `openclaw` versions pinned in `Dockerfile.base` and drives the real `renderQrTerminal` symbol the channel-login `onQr` callback uses, asserting the QR is oversized without the preload and compact with it. Wired into `regression-e2e.yaml`. - **`test/e2e/test-messaging-providers.sh` (M-WA6d):** render the QR *in-sandbox* through the baked renderer with the connect-session `NODE_OPTIONS` active and assert compact dimensions. - **`test/whatsapp-qr-compact.test.ts`:** rewritten to patch/verify the `qrcode` package shape (terminal forced small; non-terminal untouched; idempotent). ## Type of Change - [x] Code change (feature, bug fix, or refactor) ## Verification **Reporter-workflow E2E (proves rendered QR size at the real WhatsApp renderer).** The exact reporter workflow is `nemoclaw onboard` (WhatsApp enabled) → `nemoclaw <sandbox> connect` → `openclaw channels login --channel whatsapp`. A full live pairing needs a phone + WhatsApp account, but the bug is purely in QR *rendering*, which happens in the plugin's `onQr` callback before any phone interaction. The E2E installs the exact `@openclaw/whatsapp@2026.5.27` + `openclaw@2026.5.27` the sandbox bundles and drives the same `renderQrTerminal` symbol (`openclaw/plugin-sdk/media-runtime`) that `openclaw channels login --channel whatsapp` calls. - [x] **Reporter-workflow E2E ran locally** — command: `bash test/e2e/test-whatsapp-qr-compact-e2e.sh`. Log output (on PR head): ```text === Baseline (no preload) reproduces the oversized QR === [info] baseline rendered dimensions: {"rows":56,"cols":110} OK: baseline QR is oversized (56 rows >= 50) — reproduces NemoClaw#4522 === With NemoClaw compact-QR preload, the QR is scan-friendly === [info] compact rendered dimensions: {"rows":29,"cols":55} OK: compact QR fits a scan frame (29 rows <= 40) OK: preload strictly shrinks the QR (56 -> 29 rows) PASS=9 FAIL=0 WhatsApp compact-QR reporter-workflow E2E passed ``` - [x] **Pipeline E2E covers the reporter workflow** — job `whatsapp-qr-compact-e2e` in `.github/workflows/regression-e2e.yaml` runs the same `test/e2e/test-whatsapp-qr-compact-e2e.sh` on the PR head (dispatch: `gh workflow run regression-e2e.yaml -f jobs=whatsapp-qr-compact-e2e -f pr_number=5016`). The nightly `test/e2e/test-messaging-providers.sh` (check `M-WA6d`) additionally renders the QR *in-sandbox* through the baked plugin with the connect-session `NODE_OPTIONS` active and asserts the same compact dimensions. - [x] **Targeted suites pass** — `test/whatsapp-qr-compact.test.ts` (19), `test/nemoclaw-start.test.ts` (138), `test/e2e-scenario/framework-tests/e2e-migration-inventory.test.ts`, all green via `npx vitest run`. - [x] **`npm test`** — full suite green; the only reds were CI flakes in unrelated slow subprocess tests (`model-router-python`, `status-gateway-lifecycle`, `tunnel-command`) that pass in isolation and are not touched by this change. - [x] Tests added or updated for new or changed behavior - [x] No secrets, API keys, or credentials committed - [x] Reviewed with the code-review skill and CodeRabbit; actionable findings fixed (over-broad qrcode module match; `setup-node` + `persist-credentials: false` on the new CI job; failure-artifact upload; test-file size budget; zero source-shape assertions) --- Signed-off-by: Yimo Jiang <yimoj@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * WhatsApp QR rendering now forced into a compact, scan‑friendly terminal mode via a session preload; start script hardened to ensure the preload is applied in connect sessions. * **Tests** * Added an end‑to‑end test validating compact QR behavior in sandboxed sessions. * Updated unit tests to exercise preload behavior, idempotence, and multiple render shapes. * Minor test fixture tweak to initialize the preload env var during scripted runs. * **Documentation** * Clarified runtime comments about how the compact QR preload is injected. * **Chores** * CI: added a selectable regression job to run the compact QR e2e test. * **Maintenance** * Inventory updated to track the new e2e entry. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Yimo Jiang <yimoj@nvidia.com>
Summary
NemoClaw delegated in-sandbox WhatsApp QR pairing to the upstream
@openclaw/whatsappplugin, which renders the Linked-Devices QR at full size — on DGX Spark terminals it filled 50–80+ rows and hundreds of columns and could not be scanned in one phone-camera frame, and the reported1008 abnormal closuresurfaced as raw gateway text with no separation from the QR problem. This PR makes the NemoClaw-supported pairing path render a compact QR and diagnose gateway close/error conditions separately from QR rendering.Related Issue
Fixes #4522
Changes
nemoclaw-blueprint/scripts/whatsapp-qr-compact.js) that forcesqrcode-terminalinto the same{ small: true }half-block mode the host-side WeChat QR path already uses (src/ext/wechat/login.ts), roughly quartering the rendered area without changing the QR payload. It hooksModule._loadso the plugin's nested copy is patched and is idempotent.openclaw channels login --channel whatsappinvocation via the sandboxopenclaw()guard — the gateway never renders the pairing QR, so it stays off globalNODE_OPTIONS. The preload is validated undervalidate_tmp_permissions(root:444) so the sandbox user cannot tamper with aNODE_OPTIONS-injected file.OPENCLAW_GATEWAY_URLbefore pairing, and on a non-zero login exit print retry/diagnostic guidance that distinguishes a gateway close (e.g.1008) from a QR-size issue.M-WA6b/M-WA6c) that the preload + guard wiring land in the sandbox.Type of Change
Verification
npm testpasses (cli + plugin projects; the only failures were pre-existing 5000ms timeouts intest/e2e-scenario/framework-tests/*that spawn real bash and flake under parallel load — they pass in isolation and are unrelated to this change)test/whatsapp-qr-compact.test.ts,src/lib/sandbox/channels.test.ts,src/lib/sandbox/whatsapp-diagnostics.test.ts,test/nemoclaw-start.test.ts,npm run typecheck:cli— all greenLive note: no live WhatsApp sandbox/account was available, so the compact-QR rendering and gateway diagnostics are validated by hermetic tests around the renderer and the sandbox guard; the live
channels login --channel whatsappscan was not exercised end-to-end. The compact renderer assumes the bundled plugin usesqrcode-terminal(the dominant renderer, same lib as the WeChat path); the gateway validation/diagnostics apply regardless.Signed-off-by: Yimo Jiang yimoj@nvidia.com
Summary by CodeRabbit
New Features
Documentation
Tests