[GH-ISSUE #2175] fix(adb): logcat reports character units in total_bytes #723

Closed
opened 2026-10-15 18:27:42 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2175

Summary

The public read-adb-logcat command returns an incorrect normalized_result.total_bytes for captured UTF-8 text containing CJK or emoji characters. A synthetic ADB executable emitting a 37-byte log line produces total_bytes: 33; its decoded lines are correct. This is a bounded numeric result defect in the ADB observation workflow. The reproduction tests REA's handling of controlled subprocess output, and does not establish a real-device incident.

Runtime or environment

The source build was tested at c22111eca2. The separately installed published rea-agents package was version 6.4.0. Both public CLI entrypoints reproduced the behavior on Node.js 24.21.0, macOS arm64. This runtime is supported by the installation guide and package engine range; the checkout's contributor pin is Node.js 24.18.0. The producer was a strict synthetic POSIX ADB executable selected with REA_ADB_PATH, advertising 34.0.5-synthetic; no real platform-tools client, device, or emulator participated.

Steps to reproduce

With rea on PATH under a supported Node.js runtime, run this benign fixture. It selects a temporary executable that responds only to version and the exact logcat request. The fixture never invokes a real ADB client, and it isolates the CLI home and configuration directories.

import json
import os
import subprocess
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory(prefix="rea-logcat-check-") as directory:
    root = Path(directory)
    home = root / "home"
    home.mkdir()
    fixture = root / "stdout.bin"
    adb = root / "adb"
    adb.write_text('''#!/bin/sh
if [ "$#" = 1 ] && [ "$1" = version ]; then
  printf 'Android Debug Bridge version 1.0.41\nVersion 34.0.5-synthetic\nInstalled as %s\n' "$0"
  exit 0
fi
if [ "$*" = '-s emulator-5554 shell logcat -d -b main -t 500' ]; then
  cat "$LOGCAT_FIXTURE"
  exit 0
fi
exit 23
''')
    adb.chmod(0o755)
    environment = {
        "PATH": os.environ["PATH"],
        "HOME": str(home),
        "XDG_CONFIG_HOME": str(home / "config"),
        "XDG_CACHE_HOME": str(home / "cache"),
        "REA_ADB_PATH": str(adb),
        "LOGCAT_FIXTURE": str(fixture),
    }
    prefix = "08-10 13:00:00.000 I/Tag( 1): "
    for text in ["hello\n", "中文\n", "😀\n", "hello", "hello\r\n"]:
        data = (prefix + text).encode("utf-8")
        fixture.write_bytes(data)
        completed = subprocess.run(
            ["rea", "read-adb-logcat", "emulator-5554",
             "--count", "500", "--buffer", "main", "--json"],
            env=environment, capture_output=True, text=True,
            check=True, timeout=20,
        )
        result = json.loads(completed.stdout)["normalized_result"]
        print(json.dumps({
            "raw_bytes": len(data),
            "total_bytes": result["total_bytes"],
            "lines": result["lines"],
            "coverage": result["coverage"],
        }, ensure_ascii=False))

The independent local runs used each build's public scripts/rea.mjs entrypoint with the same request and byte fixtures. Real-provider reproduction is incomplete. The missing premise is an independently observed supported platform-tools/device combination delivering the same non-ASCII UTF-8 log bytes through this command. Ordinary UTF-8 messages fit REA's text decoder, but a synthetic producer is not evidence of that real-provider result.

Expected behavior

The public result field total_bytes should report a byte quantity for the captured dump. The CJK fixture contains 37 UTF-8 bytes including its final LF, so the field should be 37. Decoded lines should retain the message text. The applicable interface is the canonical logcat result schema, whose field is named total_bytes; the ADB guide documents this operation as a bounded logcat dump with a byte capture budget. The guide does not separately define this field's units in prose or promise that the field measures only the returned lines after trimming.

Actual behavior

Both tested CLI entrypoints exited with code 0 and empty stderr for all six byte fixtures. They reported the following values. The messages in lines matched the input text after REA removed line terminators, and every case reported coverage: "complete".

Captured fixture Captured bytes Reported total_bytes
ASCII message with LF 36 36
CJK message with LF 37 33
Emoji message with LF 35 33
ASCII message without final LF 35 35
ASCII message with CRLF 37 37
CJK line and second ASCII line, both with LF 74 70

The returned CJK result included lines: ["08-10 13:00:00.000 I/Tag( 1): 中文"] and total_bytes: 33. The differing LF, CRLF, and no-final-LF cases produced identical ASCII lines, so the returned line array alone cannot recover the exact captured byte count. These observations do not demonstrate a capture-budget bypass, discarded message text, or incorrect coverage.

Evidence

  • Expected source: src/contracts/adb/adbToolContracts.ts:78 advertises the bounded logcat operation and its canonical output schema; src/domain/adb/adbDeviceAnalysis.ts:496 exposes the nonnegative integer total_bytes field; docs/adb-device-analysis.md, Operations and Resource and lifecycle limits, documents the supported dump and byte capture cap. The field name establishes the byte unit; the schema's numeric constraints alone do not establish units.
  • Failure source: Independent public CLI executions against both tested builds with controlled subprocess bytes produced the values above. src/adb/AdbProvider.ts:234 decodes the captured buffer as UTF-8 and src/adb/AdbProvider.ts:785 assigns the resulting JavaScript string length to the field. The release's dist/adb/AdbProvider.js:450 has the same assignment.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: incomplete
  • Missing fact: A real supported platform-tools client and connected device or emulator producing and forwarding the non-ASCII UTF-8 bytes used by the controlled reproduction. No such provider was contacted.

The primary observation used synthetic subprocess output through the public CLI; source inspection explains the arithmetic. The request passes the CLI and shared request schemas with serial emulator-5554, count 500, buffer main, and no pid. The provider resolves the absolute synthetic executable, probes version, and executes exactly -s emulator-5554 shell logcat -d -b main -t 500. The producer exits successfully, returns empty stderr, and emits valid UTF-8 far below the capture cap. UTF-8 decoding preserves the tested messages, then .length counts UTF-16 code units. No line-count trimming occurred. MCP shares the service and provider according to source inspection; MCP was not executed in this verification.

Restoration check

Through the same public CLI request and synthetic byte fixtures, the CJK and emoji dumps should report 37 and 35 bytes respectively, and the two-line dump should report 74. The ASCII controls should continue to report 36, 35, and 37 for LF, absent final LF, and CRLF respectively, while all decoded message text remains unchanged. Check the source build and installed release separately when shipping a correction. A real-provider confirmation should independently retain the producer's raw bytes and compare them with the reported field for the same dump.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The command returns incorrect numeric evidence for valid UTF-8 subprocess output containing non-ASCII text. The failure is bounded to the reported size in the observed cases; message text, command success, and coverage remained intact. This changes a result value, rather than only presentation or wording. No downstream consumer failure or real-device incidence was observed.
  • Workaround status: unverified
  • Workaround basis: Reading lines still supplies the message text, but it does not recover the required exact byte metric because line-ending details are removed. Independently retaining the same producer stdout would allow a byte count, but this report did not establish a practical way for a normal REA caller to obtain the raw bytes of that same capture.
Originally created by @coygeek on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2175 ## Summary The public `read-adb-logcat` command returns an incorrect `normalized_result.total_bytes` for captured UTF-8 text containing CJK or emoji characters. A synthetic ADB executable emitting a 37-byte log line produces `total_bytes: 33`; its decoded `lines` are correct. This is a bounded numeric result defect in the ADB observation workflow. The reproduction tests REA's handling of controlled subprocess output, and does not establish a real-device incident. ## Runtime or environment The source build was tested at c22111eca2c4bc84a084f12711f405b3e7422a40. The separately installed published `rea-agents` package was version 6.4.0. Both public CLI entrypoints reproduced the behavior on Node.js 24.21.0, macOS arm64. This runtime is supported by the installation guide and package engine range; the checkout's contributor pin is Node.js 24.18.0. The producer was a strict synthetic POSIX ADB executable selected with `REA_ADB_PATH`, advertising `34.0.5-synthetic`; no real platform-tools client, device, or emulator participated. ## Steps to reproduce With `rea` on `PATH` under a supported Node.js runtime, run this benign fixture. It selects a temporary executable that responds only to `version` and the exact logcat request. The fixture never invokes a real ADB client, and it isolates the CLI home and configuration directories. ```python import json import os import subprocess import tempfile from pathlib import Path with tempfile.TemporaryDirectory(prefix="rea-logcat-check-") as directory: root = Path(directory) home = root / "home" home.mkdir() fixture = root / "stdout.bin" adb = root / "adb" adb.write_text('''#!/bin/sh if [ "$#" = 1 ] && [ "$1" = version ]; then printf 'Android Debug Bridge version 1.0.41\nVersion 34.0.5-synthetic\nInstalled as %s\n' "$0" exit 0 fi if [ "$*" = '-s emulator-5554 shell logcat -d -b main -t 500' ]; then cat "$LOGCAT_FIXTURE" exit 0 fi exit 23 ''') adb.chmod(0o755) environment = { "PATH": os.environ["PATH"], "HOME": str(home), "XDG_CONFIG_HOME": str(home / "config"), "XDG_CACHE_HOME": str(home / "cache"), "REA_ADB_PATH": str(adb), "LOGCAT_FIXTURE": str(fixture), } prefix = "08-10 13:00:00.000 I/Tag( 1): " for text in ["hello\n", "中文\n", "😀\n", "hello", "hello\r\n"]: data = (prefix + text).encode("utf-8") fixture.write_bytes(data) completed = subprocess.run( ["rea", "read-adb-logcat", "emulator-5554", "--count", "500", "--buffer", "main", "--json"], env=environment, capture_output=True, text=True, check=True, timeout=20, ) result = json.loads(completed.stdout)["normalized_result"] print(json.dumps({ "raw_bytes": len(data), "total_bytes": result["total_bytes"], "lines": result["lines"], "coverage": result["coverage"], }, ensure_ascii=False)) ``` The independent local runs used each build's public `scripts/rea.mjs` entrypoint with the same request and byte fixtures. Real-provider reproduction is incomplete. The missing premise is an independently observed supported platform-tools/device combination delivering the same non-ASCII UTF-8 log bytes through this command. Ordinary UTF-8 messages fit REA's text decoder, but a synthetic producer is not evidence of that real-provider result. ## Expected behavior The public result field `total_bytes` should report a byte quantity for the captured dump. The CJK fixture contains 37 UTF-8 bytes including its final LF, so the field should be 37. Decoded `lines` should retain the message text. The applicable interface is the canonical logcat result schema, whose field is named `total_bytes`; the ADB guide documents this operation as a bounded logcat dump with a byte capture budget. The guide does not separately define this field's units in prose or promise that the field measures only the returned lines after trimming. ## Actual behavior Both tested CLI entrypoints exited with code 0 and empty stderr for all six byte fixtures. They reported the following values. The messages in `lines` matched the input text after REA removed line terminators, and every case reported `coverage: "complete"`. | Captured fixture | Captured bytes | Reported total_bytes | | --- | ---: | ---: | | ASCII message with LF | 36 | 36 | | CJK message with LF | 37 | 33 | | Emoji message with LF | 35 | 33 | | ASCII message without final LF | 35 | 35 | | ASCII message with CRLF | 37 | 37 | | CJK line and second ASCII line, both with LF | 74 | 70 | The returned CJK result included `lines: ["08-10 13:00:00.000 I/Tag( 1): 中文"]` and `total_bytes: 33`. The differing LF, CRLF, and no-final-LF cases produced identical ASCII `lines`, so the returned line array alone cannot recover the exact captured byte count. These observations do not demonstrate a capture-budget bypass, discarded message text, or incorrect coverage. ## Evidence - Expected source: `src/contracts/adb/adbToolContracts.ts:78` advertises the bounded logcat operation and its canonical output schema; `src/domain/adb/adbDeviceAnalysis.ts:496` exposes the nonnegative integer `total_bytes` field; `docs/adb-device-analysis.md`, Operations and Resource and lifecycle limits, documents the supported dump and byte capture cap. The field name establishes the byte unit; the schema's numeric constraints alone do not establish units. - Failure source: Independent public CLI executions against both tested builds with controlled subprocess bytes produced the values above. `src/adb/AdbProvider.ts:234` decodes the captured buffer as UTF-8 and `src/adb/AdbProvider.ts:785` assigns the resulting JavaScript string length to the field. The release's `dist/adb/AdbProvider.js:450` has the same assignment. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: incomplete - Missing fact: A real supported platform-tools client and connected device or emulator producing and forwarding the non-ASCII UTF-8 bytes used by the controlled reproduction. No such provider was contacted. The primary observation used synthetic subprocess output through the public CLI; source inspection explains the arithmetic. The request passes the CLI and shared request schemas with serial `emulator-5554`, count 500, buffer `main`, and no pid. The provider resolves the absolute synthetic executable, probes `version`, and executes exactly `-s emulator-5554 shell logcat -d -b main -t 500`. The producer exits successfully, returns empty stderr, and emits valid UTF-8 far below the capture cap. UTF-8 decoding preserves the tested messages, then `.length` counts UTF-16 code units. No line-count trimming occurred. MCP shares the service and provider according to source inspection; MCP was not executed in this verification. ## Restoration check Through the same public CLI request and synthetic byte fixtures, the CJK and emoji dumps should report 37 and 35 bytes respectively, and the two-line dump should report 74. The ASCII controls should continue to report 36, 35, and 37 for LF, absent final LF, and CRLF respectively, while all decoded message text remains unchanged. Check the source build and installed release separately when shipping a correction. A real-provider confirmation should independently retain the producer's raw bytes and compare them with the reported field for the same dump. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The command returns incorrect numeric evidence for valid UTF-8 subprocess output containing non-ASCII text. The failure is bounded to the reported size in the observed cases; message text, command success, and coverage remained intact. This changes a result value, rather than only presentation or wording. No downstream consumer failure or real-device incidence was observed. - Workaround status: unverified - Workaround basis: Reading `lines` still supplies the message text, but it does not recover the required exact byte metric because line-ending details are removed. Independently retaining the same producer stdout would allow a byte count, but this report did not establish a practical way for a normal REA caller to obtain the raw bytes of that same capture.
bsakaguchi 2026-10-15 18:27:42 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#723
No description provided.