[GH-ISSUE #4641] [Bug] push-adb-file attributes offline and unauthorized device errors to the destination path #2421

Closed
opened 2026-10-17 21:04:23 +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/4641

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 error: device offline or unauthorized, rea push-adb-file probe-device <local-file> /sdcard/rea-repro/sample.bin --format json exits 1 with unsupported_target, but details.path and the message name /sdcard/rea-repro/sample.bin. The selected device is probe-device. With the same diagnostic, inspect-adb-device and pull-adb-file identify that serial instead.

This reproduced on both tested builds during the existence probe and during transfer after a clean absent-path probe. The raw reason and actual -s probe-device arguments remain correct. No live device or successful transfer participated.

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 provides a fake adb through REA_ADB_PATH, a local input, and fresh HOME and TMPDIR directories for each invocation. It supplies only the explicit environment, excluding client configuration overrides. For transfer failures it first returns a clean absent-path probe.
  3. The npm package can be checked with the same fixture by replacing scripts/rea.mjs in the subprocess arguments with the installed package's scripts/rea.mjs. I ran this matrix once against each build. Each run produced four destination-attributed push errors and four serial-attributed sibling controls.
python3 - <<'PYCODE'
import json, os, pathlib, subprocess, tempfile

with tempfile.TemporaryDirectory() as directory:
    root = pathlib.Path(directory).resolve()
    adb = root / 'adb'
    adb.write_text('''#!/usr/bin/env python3
import json, os, sys
with open(os.environ['ADB_LOG'], 'a') as log:
    log.write(json.dumps(sys.argv[1:]) + '\\n')
a = sys.argv[1:]
if a == ['version']:
    print('Android Debug Bridge version 1.0.41\\nVersion 34.0.5-fixture\\nInstalled as ' + __file__)
    sys.exit(0)
if a[:2] != ['-s', 'probe-device']:
    sys.exit(91)
a = a[2:]
if os.environ['FAIL_STAGE'] == 'push' and a[:3] == ['shell', 'test', '-e']:
    sys.exit(1)
print(os.environ['ADB_DIAGNOSTIC'], file=sys.stderr)
sys.exit(1)
''')
    adb.chmod(0o755)
    (root / 'sample.bin').write_bytes(b'file bytes\n')
    for mode, diagnostic in [('offline', 'error: device offline'), ('unauthorized', 'unauthorized')]:
        for stage in ['probe', 'push']:
            commands = [('push-adb-file', ['probe-device', str(root / 'sample.bin'), '/sdcard/rea-repro/sample.bin'])]
            if stage == 'probe':
                commands += [('inspect-adb-device', ['probe-device']), ('pull-adb-file', ['probe-device', '/sdcard/rea-repro/sample.bin', '--output-directory', str(root / (mode + '-pulled'))])]
            for command, args in commands:
                case = mode + '-' + stage + '-' + command
                home = root / (case + '-home')
                runtime = 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_DIAGNOSTIC': diagnostic, 'FAIL_STAGE': stage}
                run = subprocess.run(['node', 'scripts/rea.mjs', command, *args, '--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()]
                device_calls = [call for call in calls if call != ['version']]
                assert device_calls
                assert all(call[:2] == ['-s', 'probe-device'] for call in device_calls)
                assert run.returncode == 1 and data['code'] == 'unsupported_target'
                assert data['details']['reason'] == diagnostic
                assert len(device_calls) == (2 if stage == 'push' else 1)
                print(json.dumps({'case': case, 'exit': run.returncode, 'code': data['code'], 'path': data['details']['path'], 'reason': data['details']['reason']}))
PYCODE

Expected behavior

A recognized device-state refusal should identify the selected serial, consistently with inspect and pull. Device operations select a device by its serial, and REA's architecture guidance requires identity to survive boundaries. An offline or unauthorized diagnostic supplies no evidence that the destination path is unsupported. Path-specific failures such as permission denial or a missing file are outside this report.

Impact and scope

The structured failure identifies the wrong resource, so a caller using the returned identity for recovery receives the destination rather than the device. All eight push cases across the two builds reproduced the defect. No automated recovery consumer, live-device frequency, data loss, or wrong-device execution was observed. Only CLI behavior was exercised.

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded structured diagnostic replaces the selected device identity with the destination, while the raw reason and actual adb selection remain correct.
  • Workaround status: available
  • Workaround basis: Retain the request serial and read the preserved reason, or run inspect-adb-device for that serial. Both sibling controls returned the serial in this fixture.

REA version

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

Runtime and platform

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

Target and artifact context

Synthetic serial probe-device, destination /sdcard/rea-repro/sample.bin, and a local file containing file bytes followed by a newline. The fake executable contacts no Android device, emulator, adb server, or network service.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

  • Expected source: docs/adb-device-analysis.md:71-73 identifies the serial input; AGENTS.md, Architecture and Cleanup, requires preserved identity; src/adb/AdbProvider.ts:1375-1379 and :2304-2308 pass the serial for inspect and pull refusals.
  • Failure source: src/adb/AdbCommand.ts:119-134 and :205-224; src/adb/AdbProvider.ts:2425-2433; src/domain/analysisErrorProjection.ts:267-272. The fixture above directly observed the resulting CLI errors in both tested builds.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Both runs printed the same eight records:

{"case": "offline-probe-push-adb-file", "exit": 1, "code": "unsupported_target", "path": "/sdcard/rea-repro/sample.bin", "reason": "error: device offline"}
{"case": "offline-probe-inspect-adb-device", "exit": 1, "code": "unsupported_target", "path": "probe-device", "reason": "error: device offline"}
{"case": "offline-probe-pull-adb-file", "exit": 1, "code": "unsupported_target", "path": "probe-device", "reason": "error: device offline"}
{"case": "offline-push-push-adb-file", "exit": 1, "code": "unsupported_target", "path": "/sdcard/rea-repro/sample.bin", "reason": "error: device offline"}
{"case": "unauthorized-probe-push-adb-file", "exit": 1, "code": "unsupported_target", "path": "/sdcard/rea-repro/sample.bin", "reason": "unauthorized"}
{"case": "unauthorized-probe-inspect-adb-device", "exit": 1, "code": "unsupported_target", "path": "probe-device", "reason": "unauthorized"}
{"case": "unauthorized-probe-pull-adb-file", "exit": 1, "code": "unsupported_target", "path": "probe-device", "reason": "unauthorized"}
{"case": "unauthorized-push-push-adb-file", "exit": 1, "code": "unsupported_target", "path": "/sdcard/rea-repro/sample.bin", "reason": "unauthorized"}

The fixture asserts exit 1, unsupported_target, the preserved diagnostic, the correct serial in every adb device call, and one device call for probe failures or two for transfer failures. Probe errors stop before push. Transfer cases perform the clean false probe before push fails.

Typed outcome or recovery expected

Repeat the offline and unauthorized cases at both stages. The passing result should preserve the typed refusal and reason while identifying probe-device rather than the destination in details.path and the message. Keep the sibling controls, actual adb selection, and refusal before transfer on a failed probe intact.

Analysis or suspected cause

Source-derived reasoning: the existence probe passes its device path to runAdbCapture, and push passes its destination to runAdb. The shared runner uses this argument for both device-state and path-specific failures. The error projection then exposes it in details.path and the message. This suggests that those two kinds of identity are conflated; it does not prescribe a repair.

Additional context

PR #2354 introduced device-state refusal coverage for push probes. Its body shows a destination-attributed error, but its tests assert only the error code, state text, absence of an already-exists message, and no push call. They do not assert the serial or details.path. The same assertions remain in src/adb/AdbProvider.test.ts:1047-1075. This report concerns the returned identity after that refusal behavior works.

Issue #4574 concerns proceeding after an ambiguous existence probe. These are recognized device-state errors that already stop the probe, and the wrong identity also occurs during transfer after a legitimate absent-path result. 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/4641 ### 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 `error: device offline` or `unauthorized`, `rea push-adb-file probe-device <local-file> /sdcard/rea-repro/sample.bin --format json` exits 1 with `unsupported_target`, but `details.path` and the message name `/sdcard/rea-repro/sample.bin`. The selected device is `probe-device`. With the same diagnostic, `inspect-adb-device` and `pull-adb-file` identify that serial instead. This reproduced on both tested builds during the existence probe and during transfer after a clean absent-path probe. The raw reason and actual `-s probe-device` arguments remain correct. No live device or successful transfer participated. ### 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 provides a fake adb through `REA_ADB_PATH`, a local input, and fresh HOME and TMPDIR directories for each invocation. It supplies only the explicit environment, excluding client configuration overrides. For transfer failures it first returns a clean absent-path probe. 3. The npm package can be checked with the same fixture by replacing `scripts/rea.mjs` in the subprocess arguments with the installed package's `scripts/rea.mjs`. I ran this matrix once against each build. Each run produced four destination-attributed push errors and four serial-attributed sibling controls. ```sh python3 - <<'PYCODE' import json, os, pathlib, subprocess, tempfile with tempfile.TemporaryDirectory() as directory: root = pathlib.Path(directory).resolve() adb = root / 'adb' adb.write_text('''#!/usr/bin/env python3 import json, os, sys with open(os.environ['ADB_LOG'], 'a') as log: log.write(json.dumps(sys.argv[1:]) + '\\n') a = sys.argv[1:] if a == ['version']: print('Android Debug Bridge version 1.0.41\\nVersion 34.0.5-fixture\\nInstalled as ' + __file__) sys.exit(0) if a[:2] != ['-s', 'probe-device']: sys.exit(91) a = a[2:] if os.environ['FAIL_STAGE'] == 'push' and a[:3] == ['shell', 'test', '-e']: sys.exit(1) print(os.environ['ADB_DIAGNOSTIC'], file=sys.stderr) sys.exit(1) ''') adb.chmod(0o755) (root / 'sample.bin').write_bytes(b'file bytes\n') for mode, diagnostic in [('offline', 'error: device offline'), ('unauthorized', 'unauthorized')]: for stage in ['probe', 'push']: commands = [('push-adb-file', ['probe-device', str(root / 'sample.bin'), '/sdcard/rea-repro/sample.bin'])] if stage == 'probe': commands += [('inspect-adb-device', ['probe-device']), ('pull-adb-file', ['probe-device', '/sdcard/rea-repro/sample.bin', '--output-directory', str(root / (mode + '-pulled'))])] for command, args in commands: case = mode + '-' + stage + '-' + command home = root / (case + '-home') runtime = 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_DIAGNOSTIC': diagnostic, 'FAIL_STAGE': stage} run = subprocess.run(['node', 'scripts/rea.mjs', command, *args, '--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()] device_calls = [call for call in calls if call != ['version']] assert device_calls assert all(call[:2] == ['-s', 'probe-device'] for call in device_calls) assert run.returncode == 1 and data['code'] == 'unsupported_target' assert data['details']['reason'] == diagnostic assert len(device_calls) == (2 if stage == 'push' else 1) print(json.dumps({'case': case, 'exit': run.returncode, 'code': data['code'], 'path': data['details']['path'], 'reason': data['details']['reason']})) PYCODE ``` ### Expected behavior A recognized device-state refusal should identify the selected serial, consistently with inspect and pull. Device operations select a device by its serial, and REA's architecture guidance requires identity to survive boundaries. An offline or unauthorized diagnostic supplies no evidence that the destination path is unsupported. Path-specific failures such as permission denial or a missing file are outside this report. ### Impact and scope The structured failure identifies the wrong resource, so a caller using the returned identity for recovery receives the destination rather than the device. All eight push cases across the two builds reproduced the defect. No automated recovery consumer, live-device frequency, data loss, or wrong-device execution was observed. Only CLI behavior was exercised. - Impact level: P2 - Assessment status: supported - Impact basis: A bounded structured diagnostic replaces the selected device identity with the destination, while the raw reason and actual adb selection remain correct. - Workaround status: available - Workaround basis: Retain the request serial and read the preserved reason, or run inspect-adb-device for that serial. Both sibling controls returned the serial in this fixture. ### REA version Fresh main checkout at `408704827057280b240c91963ee5590fd4dcc988`, whose CLI reports 6.5.0; independently installed npm `rea-agents@6.5.0`, confirmed as the registry's latest release. ### Runtime and platform Node.js v24.18.0; Darwin arm64. Main built with npm 11.16.0. The fake adb reports Android Debug Bridge 1.0.41 and 34.0.5-fixture; these are fixture strings, not measurements of installed platform-tools. ### Target and artifact context Synthetic serial `probe-device`, destination `/sdcard/rea-repro/sample.bin`, and a local file containing `file bytes` followed by a newline. The fake executable contacts no Android device, emulator, adb server, or network service. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence - Expected source: docs/adb-device-analysis.md:71-73 identifies the serial input; AGENTS.md, Architecture and Cleanup, requires preserved identity; src/adb/AdbProvider.ts:1375-1379 and :2304-2308 pass the serial for inspect and pull refusals. - Failure source: src/adb/AdbCommand.ts:119-134 and :205-224; src/adb/AdbProvider.ts:2425-2433; src/domain/analysisErrorProjection.ts:267-272. The fixture above directly observed the resulting CLI errors in both tested builds. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Both runs printed the same eight records: ```jsonl {"case": "offline-probe-push-adb-file", "exit": 1, "code": "unsupported_target", "path": "/sdcard/rea-repro/sample.bin", "reason": "error: device offline"} {"case": "offline-probe-inspect-adb-device", "exit": 1, "code": "unsupported_target", "path": "probe-device", "reason": "error: device offline"} {"case": "offline-probe-pull-adb-file", "exit": 1, "code": "unsupported_target", "path": "probe-device", "reason": "error: device offline"} {"case": "offline-push-push-adb-file", "exit": 1, "code": "unsupported_target", "path": "/sdcard/rea-repro/sample.bin", "reason": "error: device offline"} {"case": "unauthorized-probe-push-adb-file", "exit": 1, "code": "unsupported_target", "path": "/sdcard/rea-repro/sample.bin", "reason": "unauthorized"} {"case": "unauthorized-probe-inspect-adb-device", "exit": 1, "code": "unsupported_target", "path": "probe-device", "reason": "unauthorized"} {"case": "unauthorized-probe-pull-adb-file", "exit": 1, "code": "unsupported_target", "path": "probe-device", "reason": "unauthorized"} {"case": "unauthorized-push-push-adb-file", "exit": 1, "code": "unsupported_target", "path": "/sdcard/rea-repro/sample.bin", "reason": "unauthorized"} ``` The fixture asserts exit 1, `unsupported_target`, the preserved diagnostic, the correct serial in every adb device call, and one device call for probe failures or two for transfer failures. Probe errors stop before push. Transfer cases perform the clean false probe before push fails. ### Typed outcome or recovery expected Repeat the offline and unauthorized cases at both stages. The passing result should preserve the typed refusal and reason while identifying `probe-device` rather than the destination in `details.path` and the message. Keep the sibling controls, actual adb selection, and refusal before transfer on a failed probe intact. ### Analysis or suspected cause Source-derived reasoning: the existence probe passes its device path to `runAdbCapture`, and push passes its destination to `runAdb`. The shared runner uses this argument for both device-state and path-specific failures. The error projection then exposes it in `details.path` and the message. This suggests that those two kinds of identity are conflated; it does not prescribe a repair. ### Additional context [PR #2354](https://github.com/morluto/rea/pull/2354) introduced device-state refusal coverage for push probes. Its body shows a destination-attributed error, but its tests assert only the error code, state text, absence of an already-exists message, and no push call. They do not assert the serial or `details.path`. The same assertions remain in src/adb/AdbProvider.test.ts:1047-1075. This report concerns the returned identity after that refusal behavior works. [Issue #4574](https://github.com/morluto/rea/issues/4574) concerns proceeding after an ambiguous existence probe. These are recognized device-state errors that already stop the probe, and the wrong identity also occurs during transfer after a legitimate absent-path result. 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#2421
No description provided.