openclaw - ✅(Solved) Fix [Bug]: 'channels login --channel whatsapp' QR last row malforms due to excessive ANSI escapes (qrcode small=true mode) [1 pull requests, 2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#77820Fetched 2026-05-06 06:20:53
View on GitHub
Comments
2
Participants
3
Timeline
3
Reactions
2
Timeline (top)
commented ×2cross-referenced ×1

renderQrTerminal(qr, { small: true }) in the WhatsApp channel-login flow produces a final QR-pattern row with ~10× the ANSI-escape-code density of every other row, which can break QR scanning on terminals with strict ANSI parsers, narrow buffers, or limited Unicode font support.

Error Message

In all of these cases the rest of the QR may render correctly while the last row is missing or malformed, leaving the QR unscannable without obvious error.

Root Cause

Hypothesis on the root cause (offered as analysis, not as observed code from upstream):

Fix Action

Fix / Workaround

  • WSL default font configurations (already documented as "MS Gothic font workaround" in open PR #72762; this issue describes the underlying cause).
  • SSH clients with conservative ANSI handling that drop or buffer dense escape sequences.
  • Strict JSON/log parsers ingesting CLI output downstream.
  • Some narrower terminals with per-line buffer limits.

Mitigation options for openclaw (ordered by simplicity):

PR fix notes

PR #77844: fix(media): default terminal QR to full-block mode (#77820)

Description (problem / solution / changelog)

Summary

Default renderQrTerminal and OpenClaw call sites to full-block (small: false) terminal QR output so the final row is not a pathological ANSI hotspot from the bundled qrcode renderer.

Root cause

With { type: "terminal", small: true }, node-qrcode merges QR modules using half-block glyphs; for odd-height matrices the last terminal row takes a code path that wraps each cell in SGR sequences instead of once per row. That yields a last line with far higher escape density than other rows, which breaks scanning in strict terminals, narrow buffers, or some SSH clients (see #77820).

Linked issue

Fixes #77820.

Why this change is safe

Rendering still uses the same library and the same payload; only the terminal glyph strategy changes. The QR is larger vertically but escape density stays consistent row-to-row. Callers that truly need compact mode can still pass { small: true } (upstream behavior unchanged for that explicit opt-in).

Security / runtime controls

No changes to authentication, channel credentials, gateway policy, or secret handling. This only affects how QR strings are formatted for stdout/CLI logs.

Real behavior proof

On branch, after the change, renderQrTerminal for
https://wa.me/login/2@SAMPLE-TOKEN-1234567890ABCDEF
shows median SGR sequences per non-empty row = 70, max = 70 (ratio 1.0).
On main, the same helper with default compact mode showed median ≈ 3 and max ≈ 143 on that sample (single outlier row).

Verification command (from repo root):

pnpm exec tsx -e "
(async () => {
  const { renderQrTerminal } = await import('./src/media/qr-terminal.ts');
  const sample = 'https://wa.me/login/2@SAMPLE-TOKEN-1234567890ABCDEF';
  const s = await renderQrTerminal(sample);
  const ansiSgr = new RegExp(\`\${String.fromCharCode(0x1b)}\\\\[[0-9;]*m\`, 'g');
  const escCounts = s.split(/\\r?\\n/)
    .map((line) => (line.match(ansiSgr) ?? []).length)
    .filter((c) => c > 0);
  const sorted = escCounts.toSorted((a, b) => a - b);
  const med = sorted[Math.floor(sorted.length / 2)] ?? 0;
  console.log(JSON.stringify({ median: med, max: Math.max(...escCounts) }));
})();
"

Output observed: {"median":70,"max":70}.
End-to-end channels login --channel whatsapp against a live phone was not run in this environment.

Tests run

  • pnpm exec vitest run src/media/qr-terminal.test.ts src/media/qr-terminal.render.test.ts extensions/whatsapp/src/login.coverage.test.ts
  • pnpm check:changed
  • git diff --check origin/main..HEAD

Out of scope

  • Upstream patch to node-qrcode for small: true.
  • Optional env/config toggle for compact vs full-block rendering.
  • Changing behavior for callers that explicitly pass { small: true }.

Overlap

No open upstream PR referencing #77820 was found via gh pr list --search "77820".


  • Mark as AI-assisted (tooling used for implementation and validation; proof command run locally on this branch)

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • extensions/feishu/src/app-registration.ts (modified, +1/-1)
  • extensions/whatsapp/src/login.coverage.test.ts (modified, +2/-2)
  • extensions/whatsapp/src/login.ts (modified, +1/-1)
  • extensions/whatsapp/src/session.ts (modified, +1/-1)
  • src/cli/qr-cli.ts (modified, +1/-1)
  • src/media/qr-terminal.render.test.ts (added, +20/-0)
  • src/media/qr-terminal.test.ts (modified, +1/-1)
  • src/media/qr-terminal.ts (modified, +1/-1)

Code Example

cd <openclaw source clone>
pnpm install   # if not already
node -e "const qr = require('qrcode'); qr.toString('https://wa.me/login/2@SAMPLE-TOKEN-1234567890ABCDEF', {type:'terminal', small:true}).then(s => process.stdout.write(s))"

---

[47m[30m ▄▄▄▄▄▄▄ ▄ ▄    ▄▄▄▄▄ ▄▄ ▄ ▄▄▄▄▄▄▄ [0m

---

[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀ ... [47m[30m[0m

---

[47m[30m ▄▄▄▄▄▄▄ ▄ ▄    ▄▄▄▄▄ ▄▄ ▄ ▄▄▄▄▄▄▄ [0m

---

[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

renderQrTerminal(qr, { small: true }) in the WhatsApp channel-login flow produces a final QR-pattern row with ~10× the ANSI-escape-code density of every other row, which can break QR scanning on terminals with strict ANSI parsers, narrow buffers, or limited Unicode font support.

Steps to reproduce

Reproduce using the bundled qrcode library directly, with the same options the WhatsApp login uses (per extensions/whatsapp/src/login.ts:25src/media/qr-terminal.ts):

cd <openclaw source clone>
pnpm install   # if not already
node -e "const qr = require('qrcode'); qr.toString('https://wa.me/login/2@SAMPLE-TOKEN-1234567890ABCDEF', {type:'terminal', small:true}).then(s => process.stdout.write(s))"

Inspect the last printed line versus any other row.

Expected behavior

Each row uses approximately one set of color escape codes per row. Non-final rows in the same output demonstrate this:

[47m[30m ▄▄▄▄▄▄▄ ▄ ▄    ▄▄▄▄▄ ▄▄ ▄ ▄▄▄▄▄▄▄ [0m

(one open-color sequence, content, one reset).

Actual behavior

The last row has color codes wrapped around every individual cell instead of once per row:

[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀ ... [47m[30m[0m

Reproduced on bundled qrcode 1.5.4 (latest as of 2026-05-04, npm view qrcode version). With a 25-cell-wide test QR the last row is 554 characters of mostly ANSI noise vs. ~50 chars per typical row. Bug is consistent across two different input lengths I tested.

OpenClaw version

2026.5.2 (HEAD on main, commit 5b6e552b51)

Operating system

Cross-platform — the bug is in the JS rendering of the QR string itself, not in any specific terminal. Originally noticed on Windows / Git Bash (mintty); reproduces equally in pure Linux node REPLs.

Install method

source clone (git clone openclaw/openclaw && pnpm install)

Model

NOT_ENOUGH_INFO (not model-related)

Provider / routing chain

NOT_ENOUGH_INFO

Additional provider/model setup details

NOT_ENOUGH_INFO

Logs, screenshots, and evidence

Side-by-side from the same QR rendering (raw output of the reproduction command):

Typical row (~50 chars):

[47m[30m ▄▄▄▄▄▄▄ ▄ ▄    ▄▄▄▄▄ ▄▄ ▄ ▄▄▄▄▄▄▄ [0m

Last row (~554 chars):

[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m[37m▀[47m[30m[0m

Impact and severity

Severity: Medium — silent or near-silent failure mode that breaks scannability rather than crashing. Affects users on:

  • WSL default font configurations (already documented as "MS Gothic font workaround" in open PR #72762; this issue describes the underlying cause).
  • SSH clients with conservative ANSI handling that drop or buffer dense escape sequences.
  • Strict JSON/log parsers ingesting CLI output downstream.
  • Some narrower terminals with per-line buffer limits.

In all of these cases the rest of the QR may render correctly while the last row is missing or malformed, leaving the QR unscannable without obvious error.

Additional information

Hypothesis on the root cause (offered as analysis, not as observed code from upstream):

With small: true, the qrcode library pairs every two QR-pixel rows into one terminal row using ▀ ▄ █ half-block characters. QR matrix dimensions are always odd (21×21, 25×25, 29×29, …), so the last terminal row contains only an upper-QR-pixel row with no lower partner. The library appears to fall into a per-cell color-emission path for this final partial row instead of the per-row-optimized path used for all other rows.

Mitigation options for openclaw (ordered by simplicity):

  • (A) One-liner: change small: opts.small ?? truesmall: opts.small ?? false in src/media/qr-terminal.ts:8. Produces a visually larger QR (full-block chars) but with consistent escape density across all rows, eliminating the broken-last-row mode entirely.
  • (B) Config-gated: add an OPENCLAW_QR_FORMAT=small|large env var so users on terminals that handle the dense escapes fine keep the compact rendering, and users on broken terminals can opt out.
  • (C) Upstream: PR to https://github.com/soldair/node-qrcode fixing the parity bug in the library itself. Right long-term fix but longer cycle.

Related work: open PR #72762 adds WSL-specific font guidance to the CLI — that addresses the user-visible symptom on one platform, while this issue describes the underlying root cause and a more direct fix path.

I'm happy to prepare a PR for option A or B if maintainers indicate the preferred direction. Investigation was AI-assisted; happy to redirect or close if a different approach is preferred.

extent analysis

TL;DR

The most likely fix is to change the small option to false in src/media/qr-terminal.ts to produce a visually larger QR code with consistent escape density across all rows.

Guidance

  • Verify the issue by running the provided reproduction command and inspecting the last row of the QR code output.
  • Consider implementing one of the proposed mitigation options: (A) changing small: opts.small ?? true to small: opts.small ?? false, (B) adding an OPENCLAW_QR_FORMAT env var to allow users to opt out of the compact rendering, or (C) submitting a PR to the node-qrcode library to fix the parity bug.
  • Test the chosen mitigation option to ensure it resolves the issue and does not introduce any new problems.

Example

No code snippet is provided as the issue is related to the qrcode library and the proposed fixes are configuration changes.

Notes

The issue is specific to the qrcode library and its handling of odd-sized QR matrices when rendering in compact mode. The proposed fixes aim to work around this issue, but a long-term solution would involve fixing the library itself.

Recommendation

Apply workaround (A) by changing small: opts.small ?? true to small: opts.small ?? false in src/media/qr-terminal.ts, as it is the simplest and most straightforward solution to resolve the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Each row uses approximately one set of color escape codes per row. Non-final rows in the same output demonstrate this:

[47m[30m ▄▄▄▄▄▄▄ ▄ ▄    ▄▄▄▄▄ ▄▄ ▄ ▄▄▄▄▄▄▄ [0m

(one open-color sequence, content, one reset).

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING