[GH-ISSUE #7549] [Bug] ADB push and install discard the reason a local input file was refused #4489

Closed
opened 2026-10-31 00:50:14 +00:00 by bsakaguchi · 0 comments
Owner

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

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

With a functioning fixture adb selected through REA_ADB_PATH, these commands exit 1 for either a missing local file or an existing directory:

node scripts/rea.mjs push-adb-file fixture-device "$selected" /sdcard/Download/probe.bin --format json
node scripts/rea.mjs install-adb-package fixture-device "$selected" --format json

Each returns invalid_request with the message “Analysis input is invalid. Check the arguments and try again.” The response does not identify the refused local input or state that it is missing or is not a regular file. There is no details member. Recovery tells the caller to correct “the listed fields,” but none are listed.

I also ran the production stdio MCP entry, read tools/list, and called push_adb_file with {serial: "fixture-device", local_path: selectedMissingFile, device_path: "/sdcard/Download/probe.bin"} and install_adb_package with {serial: "fixture-device", apk_path: selectedMissingFile}. Both returned isError: true and the same generic canonical error inside text content. The fixture logged only version queries; no device operation ran.

Reproduction

  1. Use the baseline checkout's built runtime and locked dependencies with Node 24.22.1 on PATH. From the checkout root, run this self-contained fixture. It isolates HOME/XDG/runtime paths, supplies adb locally, verifies the errors, and deletes its temporary files.
python3 - <<'PYTHON'
import json, os, pathlib, subprocess, tempfile

with tempfile.TemporaryDirectory(prefix='rea-input-refusal-') as directory:
    work = pathlib.Path(directory).resolve()
    for name in ['home', 'config', 'cache', 'data', 'state', 'runtime', 'directory']:
        (work / name).mkdir()
    adb = work / 'adb'
    adb.write_text('''#!/bin/sh
printf '%s\\n' "$*" >> "$0.calls"
if [ "$1" = version ]; then
  printf 'Android Debug Bridge version 1.0.41\\nVersion 34.0.5-fixture\\n'
  exit 0
fi
exit 97
''')
    adb.chmod(0o755)
    env = {'PATH': os.environ['PATH'], 'HOME': str(work / 'home'),
           'XDG_CONFIG_HOME': str(work / 'config'), 'XDG_CACHE_HOME': str(work / 'cache'),
           'XDG_DATA_HOME': str(work / 'data'), 'XDG_STATE_HOME': str(work / 'state'),
           'TMPDIR': str(work / 'runtime'), 'REA_ADB_PATH': str(adb)}
    for command in ['push-adb-file', 'install-adb-package']:
        for selected in ['missing.apk', 'directory']:
            args = [command, 'fixture-device', str(work / selected)]
            if command == 'push-adb-file':
                args += ['/sdcard/Download/probe.bin']
            result = subprocess.run(['node', 'scripts/rea.mjs', *args, '--format', 'json'],
                                    env=env, capture_output=True, text=True, timeout=30)
            error = json.loads(result.stdout)
            assert result.returncode == 1 and result.stderr == ''
            assert error['code'] == 'invalid_request' and 'details' not in error
            assert error['message'] == 'Analysis input is invalid. Check the arguments and try again.'
            assert 'Correct the listed fields' in error['remediation']['action']
            print(json.dumps({'command': command, 'input': selected, 'exit': result.returncode,
                              'code': error['code'], 'has_details': 'details' in error}))
    assert (work / 'adb.calls').read_text().splitlines() == ['version'] * 4
    control = subprocess.run(['node', 'scripts/rea.mjs', 'inspect-artifact', str(work / 'missing.apk'),
                              '--format', 'json'], env=env, capture_output=True, text=True, timeout=30)
    error = json.loads(control.stdout)
    assert control.returncode == 1 and error['code'] == 'target_unavailable'
    assert error['details']['path'] == str(work / 'missing.apk')
    assert 'ENOENT' in error['details']['reason']
    print('control: missing artifact retains path and ENOENT; adb: four version calls only')
PYTHON
  1. Observe four invalid_request responses with has_details: false, followed by the successful diagnostic control assertion. Each command/input combination reproduced twice across an initial matrix and this reduced replay. The exact block above ran once. Each missing-file MCP operation was checked once; MCP directory inputs were not checked.

Expected behavior

Retain the selected local input's identity and the filesystem fact that prevents its use, with recovery that tells the caller to select an existing regular file. A field issue naming local_path or apk_path is one suitable representation; a specific message and correction or another appropriate typed diagnostic could also carry the fact. Directory input should not be described as nonexistent.

The repository contract requires preserving caller-selected paths and meaningful failure reasons. The documented MCP recovery directs callers to correct listed fields. Reading the advertised string schema cannot identify whether this particular local file is absent or is a directory.

The published error schema makes details optional and does not require a filesystem-specific issue shape. All four observed ADB errors validate against it. This report concerns lost refusal facts and correction guidance, not an invalid error envelope. The empty-path rule in the CLI documentation is also separate: every selected input here is nonempty and absolute.

Impact and scope

Both commands reject the bad input correctly, but a consumer cannot identify the failed field or filesystem reason from the response. This is a bounded diagnostic and recovery defect, rated P2. Callers can preflight the input's existence and file type before invocation. No transfer, installation, file corruption, or agent retry loop was observed. Real-device behavior, permission failures, successful input handling, and other platforms were not tested.

Observed scope: CLI missing-file and directory cases for both commands; missing-file MCP cases for both tools. The missing-file inspect-artifact control identifies the selected path and ENOENT reason, demonstrating that these facts can survive another public inspection command's refusal.

REA version

Exact node scripts/rea.mjs --version stdout:

6.8.0

Source baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1, build 26A434; darwin arm64. The selected fixture reports Android Debug Bridge 1.0.41 and 34.0.5-fixture; these are controlled response strings, not an installed platform-tools version measurement.

Target and artifact context

No Android target was used. Inputs were a nonexistent local path and an empty directory. A local executable answers only the adb version probe and refuses every other invocation.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

The complete missing-file push error obtained through the public CLI was:

{
  "code": "invalid_request",
  "category": "invalid_input",
  "message": "Analysis input is invalid. Check the arguments and try again.",
  "retryable": false,
  "remediation": {
    "action": "Read the declared input schema or CLI usage for push_adb_file; if using MCP and the catalog may be stale, call tools/list. Correct the listed fields, then retry. Do not repeat the unchanged call."
  },
  "recovery": {
    "kind": "inspect_input_contract",
    "operation": "push_adb_file"
  }
}

Install returns the same fields, with install_adb_package in the remediation and recovery. The directory cases have that same diagnostic. MCP text content wraps the canonical error in { "error": ... } and sets isError: true.

The reduced replay printed:

{"command": "push-adb-file", "input": "missing.apk", "exit": 1, "code": "invalid_request", "has_details": false}
{"command": "push-adb-file", "input": "directory", "exit": 1, "code": "invalid_request", "has_details": false}
{"command": "install-adb-package", "input": "missing.apk", "exit": 1, "code": "invalid_request", "has_details": false}
{"command": "install-adb-package", "input": "directory", "exit": 1, "code": "invalid_request", "has_details": false}
control: missing artifact retains path and ENOENT; adb: four version calls only

Contract and source checks at the baseline:

  • AGENTS.md, Architecture and Cleanup: preserve caller-selected inputs, paths and meaningful failure reasons.
  • docs/mcp-contracts.md:141–151: canonical errors and input-contract recovery, including correction of listed fields.
  • docs/error-contract.schema.json:3–66: invalid_request requires the canonical base fields; details is optional. Independent Ajv 2020 validation accepted all four initial ADB responses.
  • docs/cli.md:42–45: empty/NUL admission is a separate contract; docs/cli.md:998–1004 describes the already-correct failure exit status.
  • src/domain/adb/adbDeviceAnalysis.ts:187–203 and :280–293: the inputs describe an existing regular file or APK. Actual tools/list schemas constrain strings to be nonempty and NUL-free; they cannot establish filesystem existence/type.

Typed outcome or recovery expected

Keep a nonzero refusal and do not attempt the device operation. Preserve which local input failed and whether it is unavailable or not a regular file, then direct correction to that input. The inspect-artifact control uses target_unavailable with path/reason details; that is a diagnostic comparison, not a requirement to change the ADB error code.

Analysis or suspected cause

Source-derived: src/adb/AdbProvider.ts:2467–2477 and :2673–2683 perform filesystem admission before push/install snapshots or device calls. Each constructs AnalysisInputError with an internal cause but no issues, user message or remediation override. The branch also combines stat failure and non-regular input; its internal cause calls both nonexistent.

src/domain/analysisErrorCore.ts:108–126 defaults issues to an empty array. src/domain/analysisErrorProjection.ts:363–379 projects issues only when present. src/domain/analysisErrorPresentation.ts:170–175 and :301–305 select generic recovery/message without an override. This explains the observed omission. Exposing the internal cause verbatim would still misdescribe directory input; the admission owner already has the type fact needed to distinguish it.

Additional context

Duplicate search included open/closed issues, open/closed/merged PRs, and issue/PR comments using command names, field names, local-file refusal wording, and ADB diagnostic terms. No matching report was found.

Open #7443 and #7404 cover empty selections becoming the working directory. Closed #5971 covers lost static Android schema issues. Closed #6079 covers a post-pull acquired-file receipt, and #4643 covers remote pull attribution. These have different triggers and acceptance conditions. No last-known-good revision or regression of a claimed fix was established.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Both public adapters omit the known failed local input and filesystem reason, preventing diagnostic consumers from identifying the corrective input from the response; invalid inputs remain refused.
  • Workaround status: available
  • Workaround basis: Callers can check local path existence and regular-file status before calling push/install; fixture preflight distinguishes the missing path from the directory without device access.
Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7549 ### 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 With a functioning fixture adb selected through `REA_ADB_PATH`, these commands exit 1 for either a missing local file or an existing directory: ```sh node scripts/rea.mjs push-adb-file fixture-device "$selected" /sdcard/Download/probe.bin --format json node scripts/rea.mjs install-adb-package fixture-device "$selected" --format json ``` Each returns `invalid_request` with the message “Analysis input is invalid. Check the arguments and try again.” The response does not identify the refused local input or state that it is missing or is not a regular file. There is no `details` member. Recovery tells the caller to correct “the listed fields,” but none are listed. I also ran the production stdio MCP entry, read `tools/list`, and called `push_adb_file` with `{serial: "fixture-device", local_path: selectedMissingFile, device_path: "/sdcard/Download/probe.bin"}` and `install_adb_package` with `{serial: "fixture-device", apk_path: selectedMissingFile}`. Both returned `isError: true` and the same generic canonical error inside text content. The fixture logged only version queries; no device operation ran. ### Reproduction 1. Use the baseline checkout's built runtime and locked dependencies with Node 24.22.1 on PATH. From the checkout root, run this self-contained fixture. It isolates HOME/XDG/runtime paths, supplies adb locally, verifies the errors, and deletes its temporary files. ```sh python3 - <<'PYTHON' import json, os, pathlib, subprocess, tempfile with tempfile.TemporaryDirectory(prefix='rea-input-refusal-') as directory: work = pathlib.Path(directory).resolve() for name in ['home', 'config', 'cache', 'data', 'state', 'runtime', 'directory']: (work / name).mkdir() adb = work / 'adb' adb.write_text('''#!/bin/sh printf '%s\\n' "$*" >> "$0.calls" if [ "$1" = version ]; then printf 'Android Debug Bridge version 1.0.41\\nVersion 34.0.5-fixture\\n' exit 0 fi exit 97 ''') adb.chmod(0o755) env = {'PATH': os.environ['PATH'], 'HOME': str(work / 'home'), 'XDG_CONFIG_HOME': str(work / 'config'), 'XDG_CACHE_HOME': str(work / 'cache'), 'XDG_DATA_HOME': str(work / 'data'), 'XDG_STATE_HOME': str(work / 'state'), 'TMPDIR': str(work / 'runtime'), 'REA_ADB_PATH': str(adb)} for command in ['push-adb-file', 'install-adb-package']: for selected in ['missing.apk', 'directory']: args = [command, 'fixture-device', str(work / selected)] if command == 'push-adb-file': args += ['/sdcard/Download/probe.bin'] result = subprocess.run(['node', 'scripts/rea.mjs', *args, '--format', 'json'], env=env, capture_output=True, text=True, timeout=30) error = json.loads(result.stdout) assert result.returncode == 1 and result.stderr == '' assert error['code'] == 'invalid_request' and 'details' not in error assert error['message'] == 'Analysis input is invalid. Check the arguments and try again.' assert 'Correct the listed fields' in error['remediation']['action'] print(json.dumps({'command': command, 'input': selected, 'exit': result.returncode, 'code': error['code'], 'has_details': 'details' in error})) assert (work / 'adb.calls').read_text().splitlines() == ['version'] * 4 control = subprocess.run(['node', 'scripts/rea.mjs', 'inspect-artifact', str(work / 'missing.apk'), '--format', 'json'], env=env, capture_output=True, text=True, timeout=30) error = json.loads(control.stdout) assert control.returncode == 1 and error['code'] == 'target_unavailable' assert error['details']['path'] == str(work / 'missing.apk') assert 'ENOENT' in error['details']['reason'] print('control: missing artifact retains path and ENOENT; adb: four version calls only') PYTHON ``` 2. Observe four `invalid_request` responses with `has_details: false`, followed by the successful diagnostic control assertion. Each command/input combination reproduced twice across an initial matrix and this reduced replay. The exact block above ran once. Each missing-file MCP operation was checked once; MCP directory inputs were not checked. ### Expected behavior Retain the selected local input's identity and the filesystem fact that prevents its use, with recovery that tells the caller to select an existing regular file. A field issue naming `local_path` or `apk_path` is one suitable representation; a specific message and correction or another appropriate typed diagnostic could also carry the fact. Directory input should not be described as nonexistent. The repository contract requires preserving caller-selected paths and meaningful failure reasons. The documented MCP recovery directs callers to correct listed fields. Reading the advertised string schema cannot identify whether this particular local file is absent or is a directory. The published error schema makes `details` optional and does not require a filesystem-specific issue shape. All four observed ADB errors validate against it. This report concerns lost refusal facts and correction guidance, not an invalid error envelope. The empty-path rule in the CLI documentation is also separate: every selected input here is nonempty and absolute. ### Impact and scope Both commands reject the bad input correctly, but a consumer cannot identify the failed field or filesystem reason from the response. This is a bounded diagnostic and recovery defect, rated P2. Callers can preflight the input's existence and file type before invocation. No transfer, installation, file corruption, or agent retry loop was observed. Real-device behavior, permission failures, successful input handling, and other platforms were not tested. Observed scope: CLI missing-file and directory cases for both commands; missing-file MCP cases for both tools. The missing-file `inspect-artifact` control identifies the selected path and ENOENT reason, demonstrating that these facts can survive another public inspection command's refusal. ### REA version Exact `node scripts/rea.mjs --version` stdout: ```text 6.8.0 ``` Source baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js v24.22.1; macOS 27.0.1, build 26A434; darwin arm64. The selected fixture reports Android Debug Bridge 1.0.41 and `34.0.5-fixture`; these are controlled response strings, not an installed platform-tools version measurement. ### Target and artifact context No Android target was used. Inputs were a nonexistent local path and an empty directory. A local executable answers only the adb version probe and refuses every other invocation. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence The complete missing-file push error obtained through the public CLI was: ```json { "code": "invalid_request", "category": "invalid_input", "message": "Analysis input is invalid. Check the arguments and try again.", "retryable": false, "remediation": { "action": "Read the declared input schema or CLI usage for push_adb_file; if using MCP and the catalog may be stale, call tools/list. Correct the listed fields, then retry. Do not repeat the unchanged call." }, "recovery": { "kind": "inspect_input_contract", "operation": "push_adb_file" } } ``` Install returns the same fields, with `install_adb_package` in the remediation and recovery. The directory cases have that same diagnostic. MCP text content wraps the canonical error in `{ "error": ... }` and sets `isError: true`. The reduced replay printed: ```text {"command": "push-adb-file", "input": "missing.apk", "exit": 1, "code": "invalid_request", "has_details": false} {"command": "push-adb-file", "input": "directory", "exit": 1, "code": "invalid_request", "has_details": false} {"command": "install-adb-package", "input": "missing.apk", "exit": 1, "code": "invalid_request", "has_details": false} {"command": "install-adb-package", "input": "directory", "exit": 1, "code": "invalid_request", "has_details": false} control: missing artifact retains path and ENOENT; adb: four version calls only ``` Contract and source checks at the baseline: - `AGENTS.md`, Architecture and Cleanup: preserve caller-selected inputs, paths and meaningful failure reasons. - `docs/mcp-contracts.md:141–151`: canonical errors and input-contract recovery, including correction of listed fields. - `docs/error-contract.schema.json:3–66`: `invalid_request` requires the canonical base fields; `details` is optional. Independent Ajv 2020 validation accepted all four initial ADB responses. - `docs/cli.md:42–45`: empty/NUL admission is a separate contract; `docs/cli.md:998–1004` describes the already-correct failure exit status. - `src/domain/adb/adbDeviceAnalysis.ts:187–203` and `:280–293`: the inputs describe an existing regular file or APK. Actual `tools/list` schemas constrain strings to be nonempty and NUL-free; they cannot establish filesystem existence/type. ### Typed outcome or recovery expected Keep a nonzero refusal and do not attempt the device operation. Preserve which local input failed and whether it is unavailable or not a regular file, then direct correction to that input. The `inspect-artifact` control uses `target_unavailable` with path/reason details; that is a diagnostic comparison, not a requirement to change the ADB error code. ### Analysis or suspected cause Source-derived: `src/adb/AdbProvider.ts:2467–2477` and `:2673–2683` perform filesystem admission before push/install snapshots or device calls. Each constructs `AnalysisInputError` with an internal cause but no issues, user message or remediation override. The branch also combines stat failure and non-regular input; its internal cause calls both nonexistent. `src/domain/analysisErrorCore.ts:108–126` defaults issues to an empty array. `src/domain/analysisErrorProjection.ts:363–379` projects issues only when present. `src/domain/analysisErrorPresentation.ts:170–175` and `:301–305` select generic recovery/message without an override. This explains the observed omission. Exposing the internal cause verbatim would still misdescribe directory input; the admission owner already has the type fact needed to distinguish it. ### Additional context Duplicate search included open/closed issues, open/closed/merged PRs, and issue/PR comments using command names, field names, local-file refusal wording, and ADB diagnostic terms. No matching report was found. Open [#7443](https://github.com/morluto/rea/issues/7443) and [#7404](https://github.com/morluto/rea/issues/7404) cover empty selections becoming the working directory. Closed [#5971](https://github.com/morluto/rea/issues/5971) covers lost static Android schema issues. Closed [#6079](https://github.com/morluto/rea/issues/6079) covers a post-pull acquired-file receipt, and [#4643](https://github.com/morluto/rea/issues/4643) covers remote pull attribution. These have different triggers and acceptance conditions. No last-known-good revision or regression of a claimed fix was established. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Both public adapters omit the known failed local input and filesystem reason, preventing diagnostic consumers from identifying the corrective input from the response; invalid inputs remain refused. - Workaround status: available - Workaround basis: Callers can check local path existence and regular-file status before calling push/install; fixture preflight distinguishes the missing path from the directory without device access.
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#4489
No description provided.