[GH-ISSUE #4643] [Bug] pull-adb-file reports the device serial as the path of a missing remote file #2423

Closed
opened 2026-10-17 21:04:27 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

CLI

Observed behavior

When adb reports a missing or unreadable remote file, rea pull-adb-file probe-device /sdcard/rea-repro/sample.bin --output-directory <fresh-directory> --format json exits 1 with unsupported_target, but details.path is probe-device. The message also identifies that serial as the unsupported target. The preserved reason names the remote file, and the actual adb arguments select the correct device and remote path.

This reproduced on both tested builds with a fake adb selected through REA_ADB_PATH. Each build reproduced two missing-file cases and one permission-denied case. The offline control correctly identified the serial, and the successful-pull control returned the correct file, byte count, and digest. No live device or MCP call was exercised.

Reproduction

  1. In a fresh checkout of the main revision below, use the pinned development toolchain, then run npm ci and npm run build:cached.
  2. From the checkout root, run the fixture below. It supplies a fake adb and fresh HOME and TMPDIR directories per invocation, with an explicit environment that excludes client configuration overrides. It contacts no device, emulator, adb server, or network service.
  3. For the published package, install rea-agents at the release stated below into a disposable directory and replace scripts/rea.mjs in the first line with that package's scripts/rea.mjs path. I ran the same matrix against both builds.
python3 - scripts/rea.mjs <<'PYCODE'
import hashlib
import json
import os
import pathlib
import subprocess
import sys
import tempfile

entry = str(pathlib.Path(sys.argv[1]).resolve())
with tempfile.TemporaryDirectory() as directory:
    root = pathlib.Path(directory).resolve()
    remote = '/sdcard/rea-repro/sample.bin'
    adb = root / 'adb'
    adb.write_text('''#!/usr/bin/env python3
import json, os, pathlib, sys
args = sys.argv[1:]
with open(os.environ['ADB_LOG'], 'a') as log:
    log.write(json.dumps(args) + '\\n')
if args == ['version']:
    print('Android Debug Bridge version 1.0.41\\nVersion 34.0.5-fixture\\nInstalled as ' + __file__)
    sys.exit(0)
if len(args) != 5 or args[:4] != ['-s', 'probe-device', 'pull', '/sdcard/rea-repro/sample.bin']:
    sys.exit(91)
mode = os.environ['ADB_MODE']
if mode == 'success':
    pathlib.Path(args[4]).write_bytes(b'fixture bytes\\n')
    sys.exit(0)
reasons = {'missing': "adb: error: failed to stat remote object '/sdcard/rea-repro/sample.bin': No such file or directory", 'permission': "adb: error: failed to open '/sdcard/rea-repro/sample.bin': Permission denied", 'offline': 'error: device offline'}
print(reasons[mode], file=sys.stderr)
sys.exit(1)
''')
    adb.chmod(0o755)
    for case, mode in [('missing-1', 'missing'), ('missing-2', 'missing'), ('permission', 'permission'), ('offline', 'offline'), ('success', 'success')]:
        home, runtime = root / (case + '-home'), root / (case + '-runtime')
        home.mkdir()
        runtime.mkdir()
        log = root / (case + '.jsonl')
        env = {'PATH': os.environ['PATH'], 'HOME': str(home), 'TMPDIR': str(runtime), 'REA_ADB_PATH': str(adb), 'ADB_LOG': str(log), 'ADB_MODE': mode}
        run = subprocess.run(['node', entry, 'pull-adb-file', 'probe-device', remote, '--output-directory', str(root / (case + '-output')), '--format', 'json'], env=env, text=True, capture_output=True)
        data = json.loads(run.stdout)
        calls = [json.loads(line) for line in log.read_text().splitlines()]
        assert len(calls) == 2 and calls[0] == ['version'], calls
        assert calls[1][:4] == ['-s', 'probe-device', 'pull', remote], calls
        if mode == 'success':
            result = data['normalized_result']
            assert run.returncode == 0 and result['serial'] == 'probe-device'
            assert result['device_path'] == remote
            assert pathlib.Path(result['local_path']).read_bytes() == b'fixture bytes\n'
            assert result['bytes'] == 14 and result['sha256'] == hashlib.sha256(b'fixture bytes\n').hexdigest()
            print(json.dumps({'case': case, 'exit': run.returncode, 'bytes': result['bytes'], 'device_path': result['device_path'], 'sha256': result['sha256']}))
        else:
            assert run.returncode == 1 and data['code'] == 'unsupported_target'
            assert data['details']['operation'] == 'pull_adb_file'
            assert data['details']['path'] == 'probe-device'
            assert 'probe-device' in data['message']
            assert mode == 'offline' or remote in data['details']['reason']
            print(json.dumps({'case': case, 'exit': run.returncode, 'code': data['code'], 'path': data['details']['path'], 'reason': data['details']['reason'], 'message': data['message']}))
PYCODE

Expected behavior

For a refusal that identifies the supplied remote file as missing or unreadable, details.path and the message target should identify /sdcard/rea-repro/sample.bin. Device-state failures should continue to identify probe-device. REA's contract distinguishes the serial from the absolute device filesystem path and requires identity and meaningful caller-selected inputs to survive boundaries. A missing or unreadable file should still fail to transfer.

Impact and scope

The structured error attributes a path-specific refusal to the wrong input, although the raw reason retains the remote path. Across both builds, missing-file attribution failed in 4/4 cases and permission attribution in 2/2 cases. These counts describe the simulated CLI cases only. Live-device frequency, MCP behavior, and downstream recovery consumers were not tested. No data loss or wrong-device execution was observed.

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Consumers of details.path receive the device serial for a missing or unreadable remote file, a bounded diagnostic defect with the original adb reason preserved.
  • Workaround status: available
  • Workaround basis: Retain the request device_path and read details.reason, which preserved the remote path in every observed path-specific refusal.

REA version

Fresh main checkout at 150e996a86f2bec4982ba87f37bf502a8d5e8214, whose CLI reports 6.5.0; separately installed npm rea-agents@6.5.0, confirmed as the registry's latest release.

Runtime and platform

Node.js v24.18.0; macOS 27.0.1, Darwin arm64. Main built with npm 11.16.0. The fake adb reports Android Debug Bridge 1.0.41 and 34.0.5-fixture; those are fixture strings, not installed platform-tools measurements.

Target and artifact context

Synthetic device serial probe-device and remote file /sdcard/rea-repro/sample.bin. The success control writes the 14-byte payload fixture bytes followed by a newline into the requested host destination. Other modes return selected diagnostics without writing a file.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

  • Expected source: AGENTS.md, Architecture and Cleanup, requires preserved identity; docs/tool-design.md:48-55 requires explicit target identity and preservation of meaningful target choices; src/domain/adb/adbDeviceAnalysis.ts:120-130 distinguishes serial and device_path; docs/adb-device-analysis.md:57 documents the command.
  • Failure source: src/adb/AdbProvider.ts:2304-2311 supplies the serial as the error target for pull; src/adb/AdbCommand.ts:135-144 uses that target for path-specific refusals; src/domain/analysisErrorProjection.ts:267-272 exposes it in details.path. The public CLI fixture directly observed the resulting errors on both builds.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Both runs printed the same records. The smallest failure output was:

{"case":"missing-1","exit":1,"code":"unsupported_target","path":"probe-device","reason":"adb: error: failed to stat remote object '/sdcard/rea-repro/sample.bin': No such file or directory","message":"Unsupported target for pull_adb_file at probe-device: adb: error: failed to stat remote object '/sdcard/rea-repro/sample.bin': No such file or directory"}

The permission case also returned path: probe-device, preserving adb: error: failed to open '/sdcard/rea-repro/sample.bin': Permission denied. The offline control preserved error: device offline and identified probe-device. The success control returned 14 bytes and SHA-256 eac2a5a0837a324061f4b038b4c0af8c011bf9c47b595cda9459fd9c90651623, matching an independent hashlib calculation.

The fixture checks that each call performs exactly one version query and one -s probe-device pull /sdcard/rea-repro/sample.bin <host-destination> invocation. It checks refusal exit codes, operation, structured path, message attribution, and preserved remote-path evidence, plus the successful file contents and digest.

Typed outcome or recovery expected

Repeat the same matrix. Missing-file and permission-denied cases should preserve the typed refusal and original reason while identifying the remote path in details.path and the message target. Offline pull should retain serial attribution, and successful pull should retain the correct remote path, bytes, and digest. The current assertions for path-specific failures demonstrate the defect and should expect the remote path when checking a correction.

Analysis or suspected cause

Source-derived reasoning: pull supplies the serial as the shared runner's error target. The runner reuses that target for device-state and path-specific diagnostics, and the error projection exposes it unchanged. This explains the observed attribution without suggesting that adb selected the wrong device or file.

Additional context

Related: issue #4641 covers push offline and unauthorized device-state errors attributed to the destination path, and explicitly excludes path-specific errors. Fixing push device-state attribution can leave this pull failure unchanged. This report requires remote-path attribution for path-specific pull errors while preserving serial attribution for offline pull. No last-known-good version was established.

Originally created by @coygeek on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4643 ### Before submitting - [X] I searched the open and closed issues for a related report. - [X] I removed credentials, tokens, private paths, and sensitive target data. ### Area CLI ### Observed behavior When adb reports a missing or unreadable remote file, `rea pull-adb-file probe-device /sdcard/rea-repro/sample.bin --output-directory <fresh-directory> --format json` exits 1 with `unsupported_target`, but `details.path` is `probe-device`. The message also identifies that serial as the unsupported target. The preserved reason names the remote file, and the actual adb arguments select the correct device and remote path. This reproduced on both tested builds with a fake adb selected through `REA_ADB_PATH`. Each build reproduced two missing-file cases and one permission-denied case. The offline control correctly identified the serial, and the successful-pull control returned the correct file, byte count, and digest. No live device or MCP call was exercised. ### Reproduction 1. In a fresh checkout of the main revision below, use the pinned development toolchain, then run `npm ci` and `npm run build:cached`. 2. From the checkout root, run the fixture below. It supplies a fake adb and fresh HOME and TMPDIR directories per invocation, with an explicit environment that excludes client configuration overrides. It contacts no device, emulator, adb server, or network service. 3. For the published package, install `rea-agents` at the release stated below into a disposable directory and replace `scripts/rea.mjs` in the first line with that package's `scripts/rea.mjs` path. I ran the same matrix against both builds. ```sh python3 - scripts/rea.mjs <<'PYCODE' import hashlib import json import os import pathlib import subprocess import sys import tempfile entry = str(pathlib.Path(sys.argv[1]).resolve()) with tempfile.TemporaryDirectory() as directory: root = pathlib.Path(directory).resolve() remote = '/sdcard/rea-repro/sample.bin' adb = root / 'adb' adb.write_text('''#!/usr/bin/env python3 import json, os, pathlib, sys args = sys.argv[1:] with open(os.environ['ADB_LOG'], 'a') as log: log.write(json.dumps(args) + '\\n') if args == ['version']: print('Android Debug Bridge version 1.0.41\\nVersion 34.0.5-fixture\\nInstalled as ' + __file__) sys.exit(0) if len(args) != 5 or args[:4] != ['-s', 'probe-device', 'pull', '/sdcard/rea-repro/sample.bin']: sys.exit(91) mode = os.environ['ADB_MODE'] if mode == 'success': pathlib.Path(args[4]).write_bytes(b'fixture bytes\\n') sys.exit(0) reasons = {'missing': "adb: error: failed to stat remote object '/sdcard/rea-repro/sample.bin': No such file or directory", 'permission': "adb: error: failed to open '/sdcard/rea-repro/sample.bin': Permission denied", 'offline': 'error: device offline'} print(reasons[mode], file=sys.stderr) sys.exit(1) ''') adb.chmod(0o755) for case, mode in [('missing-1', 'missing'), ('missing-2', 'missing'), ('permission', 'permission'), ('offline', 'offline'), ('success', 'success')]: home, runtime = root / (case + '-home'), root / (case + '-runtime') home.mkdir() runtime.mkdir() log = root / (case + '.jsonl') env = {'PATH': os.environ['PATH'], 'HOME': str(home), 'TMPDIR': str(runtime), 'REA_ADB_PATH': str(adb), 'ADB_LOG': str(log), 'ADB_MODE': mode} run = subprocess.run(['node', entry, 'pull-adb-file', 'probe-device', remote, '--output-directory', str(root / (case + '-output')), '--format', 'json'], env=env, text=True, capture_output=True) data = json.loads(run.stdout) calls = [json.loads(line) for line in log.read_text().splitlines()] assert len(calls) == 2 and calls[0] == ['version'], calls assert calls[1][:4] == ['-s', 'probe-device', 'pull', remote], calls if mode == 'success': result = data['normalized_result'] assert run.returncode == 0 and result['serial'] == 'probe-device' assert result['device_path'] == remote assert pathlib.Path(result['local_path']).read_bytes() == b'fixture bytes\n' assert result['bytes'] == 14 and result['sha256'] == hashlib.sha256(b'fixture bytes\n').hexdigest() print(json.dumps({'case': case, 'exit': run.returncode, 'bytes': result['bytes'], 'device_path': result['device_path'], 'sha256': result['sha256']})) else: assert run.returncode == 1 and data['code'] == 'unsupported_target' assert data['details']['operation'] == 'pull_adb_file' assert data['details']['path'] == 'probe-device' assert 'probe-device' in data['message'] assert mode == 'offline' or remote in data['details']['reason'] print(json.dumps({'case': case, 'exit': run.returncode, 'code': data['code'], 'path': data['details']['path'], 'reason': data['details']['reason'], 'message': data['message']})) PYCODE ``` ### Expected behavior For a refusal that identifies the supplied remote file as missing or unreadable, `details.path` and the message target should identify `/sdcard/rea-repro/sample.bin`. Device-state failures should continue to identify `probe-device`. REA's contract distinguishes the serial from the absolute device filesystem path and requires identity and meaningful caller-selected inputs to survive boundaries. A missing or unreadable file should still fail to transfer. ### Impact and scope The structured error attributes a path-specific refusal to the wrong input, although the raw reason retains the remote path. Across both builds, missing-file attribution failed in 4/4 cases and permission attribution in 2/2 cases. These counts describe the simulated CLI cases only. Live-device frequency, MCP behavior, and downstream recovery consumers were not tested. No data loss or wrong-device execution was observed. - Impact level: P2 - Assessment status: supported - Impact basis: Consumers of details.path receive the device serial for a missing or unreadable remote file, a bounded diagnostic defect with the original adb reason preserved. - Workaround status: available - Workaround basis: Retain the request device_path and read details.reason, which preserved the remote path in every observed path-specific refusal. ### REA version Fresh main checkout at `150e996a86f2bec4982ba87f37bf502a8d5e8214`, whose CLI reports 6.5.0; separately installed npm `rea-agents@6.5.0`, confirmed as the registry's latest release. ### Runtime and platform Node.js v24.18.0; macOS 27.0.1, Darwin arm64. Main built with npm 11.16.0. The fake adb reports Android Debug Bridge 1.0.41 and 34.0.5-fixture; those are fixture strings, not installed platform-tools measurements. ### Target and artifact context Synthetic device serial `probe-device` and remote file `/sdcard/rea-repro/sample.bin`. The success control writes the 14-byte payload `fixture bytes` followed by a newline into the requested host destination. Other modes return selected diagnostics without writing a file. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence - Expected source: AGENTS.md, Architecture and Cleanup, requires preserved identity; docs/tool-design.md:48-55 requires explicit target identity and preservation of meaningful target choices; src/domain/adb/adbDeviceAnalysis.ts:120-130 distinguishes serial and device_path; docs/adb-device-analysis.md:57 documents the command. - Failure source: src/adb/AdbProvider.ts:2304-2311 supplies the serial as the error target for pull; src/adb/AdbCommand.ts:135-144 uses that target for path-specific refusals; src/domain/analysisErrorProjection.ts:267-272 exposes it in details.path. The public CLI fixture directly observed the resulting errors on both builds. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Both runs printed the same records. The smallest failure output was: ```json {"case":"missing-1","exit":1,"code":"unsupported_target","path":"probe-device","reason":"adb: error: failed to stat remote object '/sdcard/rea-repro/sample.bin': No such file or directory","message":"Unsupported target for pull_adb_file at probe-device: adb: error: failed to stat remote object '/sdcard/rea-repro/sample.bin': No such file or directory"} ``` The permission case also returned `path: probe-device`, preserving `adb: error: failed to open '/sdcard/rea-repro/sample.bin': Permission denied`. The offline control preserved `error: device offline` and identified `probe-device`. The success control returned 14 bytes and SHA-256 `eac2a5a0837a324061f4b038b4c0af8c011bf9c47b595cda9459fd9c90651623`, matching an independent hashlib calculation. The fixture checks that each call performs exactly one version query and one `-s probe-device pull /sdcard/rea-repro/sample.bin <host-destination>` invocation. It checks refusal exit codes, operation, structured path, message attribution, and preserved remote-path evidence, plus the successful file contents and digest. ### Typed outcome or recovery expected Repeat the same matrix. Missing-file and permission-denied cases should preserve the typed refusal and original reason while identifying the remote path in details.path and the message target. Offline pull should retain serial attribution, and successful pull should retain the correct remote path, bytes, and digest. The current assertions for path-specific failures demonstrate the defect and should expect the remote path when checking a correction. ### Analysis or suspected cause Source-derived reasoning: pull supplies the serial as the shared runner's error target. The runner reuses that target for device-state and path-specific diagnostics, and the error projection exposes it unchanged. This explains the observed attribution without suggesting that adb selected the wrong device or file. ### Additional context Related: [issue #4641](https://github.com/morluto/rea/issues/4641) covers push offline and unauthorized device-state errors attributed to the destination path, and explicitly excludes path-specific errors. Fixing push device-state attribution can leave this pull failure unchanged. This report requires remote-path attribution for path-specific pull errors while preserving serial attribution for offline pull. No last-known-good version was established.
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#2423
No description provided.