[GH-ISSUE #4654] [Bug] Package inspection reports zero requested permissions when package details are unavailable #2426

Closed
opened 2026-10-17 21:04:30 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Process capture or runtime observation

Observed behavior

When fake adb returns a package dump with pkg=null and a stored version code, inspect-adb-package exits 0 and returns requested_permissions_count: 0 with coverage: "complete". The dump supplies no parsed package or requested-permissions list. Both tested builds reproduced this result in two of two runs. A loaded-package control without requests returned 0, and a control with two listed requests returned 2. Every call exited 0 with empty stderr.

Reproduction

Run this from a built source checkout or unpacked npm package root. For the source build, I used npm ci and npm run build:cached with the pinned Node/npm toolchain. For the release tarball, I installed dependencies with npm install --ignore-scripts. The script uses an absolute Node executable, a fresh HOME for each call, an explicit fake adb, and no inherited configuration variables.

python3 - scripts/rea.mjs <<'PY'
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile

node = subprocess.check_output(["node", "-p", "process.execPath"], text=True).strip()
cli = str(Path(sys.argv[1]).resolve())
base = "Packages:\n  Package [com.example] (abcdef):\n    appId=10234\n"
unavailable = base + "    pkg=null\n    codePath=/data/app/com.example\n    versionCode=45\n    pkgFlags=[ HAS_CODE ]\n    User 0: installed=true\n"
loaded = base + "    pkg=Package{abcdef com.example}\n    versionCode=45 minSdk=24 targetSdk=34\n    versionName=1.2.3\n"
populated = loaded + "    requested permissions:\n      android.permission.INTERNET\n      android.permission.ACCESS_NETWORK_STATE\n"
fake = r'''#!/bin/sh
if [ "$1" = version ]; then
  printf 'Android Debug Bridge version 1.0.41\nVersion 34.0.5-fixture\nInstalled as %s\n' "$0"
  exit 0
fi
if [ "$#" = 6 ] && [ "$1" = -s ] && [ "$2" = emulator-5554 ] && [ "$3" = shell ] && [ "$4" = dumpsys ] && [ "$5" = package ] && [ "$6" = com.example ]; then
  cat "$ADB_FIXTURE"
  exit 0
fi
exit 1
'''
with tempfile.TemporaryDirectory() as root:
    root = Path(root)
    adb = root / "adb"
    adb.write_text(fake)
    adb.chmod(0o755)
    cases = [("unavailable", unavailable), ("unavailable", unavailable), ("loaded-empty", loaded), ("loaded-two", populated)]
    for index, (name, text) in enumerate(cases):
        fixture = root / "package.txt"
        fixture.write_text(text)
        home = root / f"home-{index}"
        home.mkdir()
        env = {"HOME": str(home), "PATH": str(Path(node).parent) + os.pathsep + os.defpath, "REA_ADB_PATH": str(adb), "ADB_FIXTURE": str(fixture)}
        result = subprocess.run([node, cli, "inspect-adb-package", "emulator-5554", "com.example", "--format", "json", "--full-output"], env=env, cwd=home, text=True, capture_output=True)
        assert result.returncode == 0, result.stdout + result.stderr
        assert not result.stderr, result.stderr
        evidence = json.loads(result.stdout)["data"]
        answer = evidence["normalized_result"]
        fields = ["version_name", "version_code", "requested_permissions_count", "coverage"]
        print(json.dumps({"case": name, "exit": result.returncode, "result": {key: answer[key] for key in fields}, "limitations": evidence["limitations"]}))
        raw = subprocess.check_output([str(adb), "-s", "emulator-5554", "shell", "dumpsys", "package", "com.example"], env=env, text=True)
        assert raw == text
        conservative_count = None if "pkg=null" in raw else answer["requested_permissions_count"]
        assert conservative_count == {"unavailable": None, "loaded-empty": 0, "loaded-two": 2}[name]
PY

This executes the unavailable case twice and each control once per build. It also checks the caller-side workaround against all cases. No physical device or adopted-storage transition was exercised.

Expected behavior

Keep an unavailable permission inventory unknown while retaining zero for a known empty inventory. The repository requires unresolved applicability and unknowns to survive normalization. Android omits the requested section both for a loaded package with no requests and when the parsed package is unavailable. A missing header alone therefore does not establish zero. The result should express the unavailable count or its uncertainty explicitly rather than present it as a complete zero inventory.

Impact and scope

The result gives a false known-zero requested-permission count for the unavailable parsed-package shape. Android source identifies ejected adopted storage as one situation with no parsed package. Device-state frequency and downstream effects were not measured.

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded package-inspection result replaces unavailable requested-permission information with an affirmative zero and complete coverage.
  • Workaround status: available
  • Workaround basis: Independently read the raw package dump and treat the count as unknown when it declares pkg=null. The reproduction checks that the fake adb returns the exact input and that this rule preserves the loaded-empty count of 0 and populated count of 2. It requires an additional raw-output check because the normalized result does not expose parsed-package availability.

REA version

Current-main source commit 150e996a86 and the published npm release rea-agents@6.5.0. Both launchers reported 6.5.0.

Runtime and platform

Node.js v24.18.0; Darwin arm64. The source build used npm 11.16.0. The fake client reports adb 34.0.5-fixture; no platform-tools installation or Android device was involved.

Target and artifact context

Reduced single-package dumpsys output for com.example, following Android's package-settings branches for an unavailable parsed package. Platform reachability is source-derived; the REA results were observed through the CLI.

Interface

CLI

Earliest failure stage

Capture or observation

Relevant output or evidence

  • Expected source: AGENTS.md:19-31, docs/adb-device-analysis.md:93, and the nullable requested-permission count in src/domain/adb/adbDeviceAnalysis.ts:542.
  • Failure source: Executed CLI results below; src/adb/AdbShellOutput.ts:250-291 and src/adb/AdbProvider.ts:1510-1548 in the main build.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Both builds returned this fragment for the unavailable case, twice per build:

{"version_name":null,"requested_permissions_count":0,"coverage":"complete"}

Both controls returned complete coverage; their counts were 0 and 2. The stored version code was the string "45" on main and the number 45 in the release; that representation difference does not affect the failure. The limitations were:

Artifact identity is unavailable for this observation.
Fields are a fixed projection of dumpsys package output; version-dependent sections are ignored rather than guessed
requested_permissions_count counts the listed requested permissions section only

Source-derived format check: Android's release-tagged package-settings producer obtains the nullable parsed package at line 4629, prints pkg at line 4709 and the stored version code at line 4730, and guards the requested list on a non-null package with a nonempty list at lines 4993-5005. Its matching-package loop at lines 5160-5196 still calls the dump for a stored package with no parsed package. Android's removal helper documents an ejected adopted-storage device as a case without a parsed package. These are source checks, not a physical-device reproduction.

Typed outcome or recovery expected

For the unavailable fixture, stop asserting a known zero inventory, for example by returning a null count with an explicit limitation or appropriate partial coverage. Preserve 0 for the loaded-empty control and 2 for the populated control. Making every missing requested header unknown would fail the empty-list control.

Analysis or suspected cause

Source-derived: the parser starts its counter at zero and does not track parsed-package availability. The provider accepts the nonempty dump, forwards that count, and determines complete coverage from the version code or name. The listed-section limitation does not distinguish an unavailable list from a known empty inventory. MCP shares the provider path but was not executed.

Additional context

The open and closed issue and PR searches found no report covering unavailable package details and a false zero count. PR #2359 concerns restricted markers on entries in an existing permissions section. Issue #3943 concerns installer absence, and issue #4608 concerns window-focus absence. Those have different restoration checks. Normal empty-list dumps are not the failure reported here.

Originally created by @coygeek on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4654 ### 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 Process capture or runtime observation ### Observed behavior When fake adb returns a package dump with `pkg=null` and a stored version code, `inspect-adb-package` exits 0 and returns `requested_permissions_count: 0` with `coverage: "complete"`. The dump supplies no parsed package or requested-permissions list. Both tested builds reproduced this result in two of two runs. A loaded-package control without requests returned 0, and a control with two listed requests returned 2. Every call exited 0 with empty stderr. ### Reproduction Run this from a built source checkout or unpacked npm package root. For the source build, I used `npm ci` and `npm run build:cached` with the pinned Node/npm toolchain. For the release tarball, I installed dependencies with `npm install --ignore-scripts`. The script uses an absolute Node executable, a fresh HOME for each call, an explicit fake adb, and no inherited configuration variables. ```sh python3 - scripts/rea.mjs <<'PY' import json import os from pathlib import Path import subprocess import sys import tempfile node = subprocess.check_output(["node", "-p", "process.execPath"], text=True).strip() cli = str(Path(sys.argv[1]).resolve()) base = "Packages:\n Package [com.example] (abcdef):\n appId=10234\n" unavailable = base + " pkg=null\n codePath=/data/app/com.example\n versionCode=45\n pkgFlags=[ HAS_CODE ]\n User 0: installed=true\n" loaded = base + " pkg=Package{abcdef com.example}\n versionCode=45 minSdk=24 targetSdk=34\n versionName=1.2.3\n" populated = loaded + " requested permissions:\n android.permission.INTERNET\n android.permission.ACCESS_NETWORK_STATE\n" fake = r'''#!/bin/sh if [ "$1" = version ]; then printf 'Android Debug Bridge version 1.0.41\nVersion 34.0.5-fixture\nInstalled as %s\n' "$0" exit 0 fi if [ "$#" = 6 ] && [ "$1" = -s ] && [ "$2" = emulator-5554 ] && [ "$3" = shell ] && [ "$4" = dumpsys ] && [ "$5" = package ] && [ "$6" = com.example ]; then cat "$ADB_FIXTURE" exit 0 fi exit 1 ''' with tempfile.TemporaryDirectory() as root: root = Path(root) adb = root / "adb" adb.write_text(fake) adb.chmod(0o755) cases = [("unavailable", unavailable), ("unavailable", unavailable), ("loaded-empty", loaded), ("loaded-two", populated)] for index, (name, text) in enumerate(cases): fixture = root / "package.txt" fixture.write_text(text) home = root / f"home-{index}" home.mkdir() env = {"HOME": str(home), "PATH": str(Path(node).parent) + os.pathsep + os.defpath, "REA_ADB_PATH": str(adb), "ADB_FIXTURE": str(fixture)} result = subprocess.run([node, cli, "inspect-adb-package", "emulator-5554", "com.example", "--format", "json", "--full-output"], env=env, cwd=home, text=True, capture_output=True) assert result.returncode == 0, result.stdout + result.stderr assert not result.stderr, result.stderr evidence = json.loads(result.stdout)["data"] answer = evidence["normalized_result"] fields = ["version_name", "version_code", "requested_permissions_count", "coverage"] print(json.dumps({"case": name, "exit": result.returncode, "result": {key: answer[key] for key in fields}, "limitations": evidence["limitations"]})) raw = subprocess.check_output([str(adb), "-s", "emulator-5554", "shell", "dumpsys", "package", "com.example"], env=env, text=True) assert raw == text conservative_count = None if "pkg=null" in raw else answer["requested_permissions_count"] assert conservative_count == {"unavailable": None, "loaded-empty": 0, "loaded-two": 2}[name] PY ``` This executes the unavailable case twice and each control once per build. It also checks the caller-side workaround against all cases. No physical device or adopted-storage transition was exercised. ### Expected behavior Keep an unavailable permission inventory unknown while retaining zero for a known empty inventory. The repository requires unresolved applicability and unknowns to survive normalization. Android omits the requested section both for a loaded package with no requests and when the parsed package is unavailable. A missing header alone therefore does not establish zero. The result should express the unavailable count or its uncertainty explicitly rather than present it as a complete zero inventory. ### Impact and scope The result gives a false known-zero requested-permission count for the unavailable parsed-package shape. Android source identifies ejected adopted storage as one situation with no parsed package. Device-state frequency and downstream effects were not measured. - Impact level: P2 - Assessment status: supported - Impact basis: A bounded package-inspection result replaces unavailable requested-permission information with an affirmative zero and complete coverage. - Workaround status: available - Workaround basis: Independently read the raw package dump and treat the count as unknown when it declares pkg=null. The reproduction checks that the fake adb returns the exact input and that this rule preserves the loaded-empty count of 0 and populated count of 2. It requires an additional raw-output check because the normalized result does not expose parsed-package availability. ### REA version Current-main source commit 150e996a86f2bec4982ba87f37bf502a8d5e8214 and the published npm release rea-agents@6.5.0. Both launchers reported 6.5.0. ### Runtime and platform Node.js v24.18.0; Darwin arm64. The source build used npm 11.16.0. The fake client reports adb 34.0.5-fixture; no platform-tools installation or Android device was involved. ### Target and artifact context Reduced single-package dumpsys output for `com.example`, following Android's package-settings branches for an unavailable parsed package. Platform reachability is source-derived; the REA results were observed through the CLI. ### Interface CLI ### Earliest failure stage Capture or observation ### Relevant output or evidence - Expected source: `AGENTS.md:19-31`, `docs/adb-device-analysis.md:93`, and the nullable requested-permission count in `src/domain/adb/adbDeviceAnalysis.ts:542`. - Failure source: Executed CLI results below; `src/adb/AdbShellOutput.ts:250-291` and `src/adb/AdbProvider.ts:1510-1548` in the main build. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Both builds returned this fragment for the unavailable case, twice per build: ```json {"version_name":null,"requested_permissions_count":0,"coverage":"complete"} ``` Both controls returned complete coverage; their counts were 0 and 2. The stored version code was the string `"45"` on main and the number `45` in the release; that representation difference does not affect the failure. The limitations were: ```text Artifact identity is unavailable for this observation. Fields are a fixed projection of dumpsys package output; version-dependent sections are ignored rather than guessed requested_permissions_count counts the listed requested permissions section only ``` Source-derived format check: [Android's release-tagged package-settings producer](https://github.com/aosp-mirror/platform_frameworks_base/blob/android-14.0.0_r1/services/core/java/com/android/server/pm/Settings.java) obtains the nullable parsed package at line 4629, prints `pkg` at line 4709 and the stored version code at line 4730, and guards the requested list on a non-null package with a nonempty list at lines 4993-5005. Its matching-package loop at lines 5160-5196 still calls the dump for a stored package with no parsed package. [Android's removal helper](https://github.com/aosp-mirror/platform_frameworks_base/blob/android-14.0.0_r1/services/core/java/com/android/server/pm/RemovePackageHelper.java#L293-L296) documents an ejected adopted-storage device as a case without a parsed package. These are source checks, not a physical-device reproduction. ### Typed outcome or recovery expected For the unavailable fixture, stop asserting a known zero inventory, for example by returning a null count with an explicit limitation or appropriate partial coverage. Preserve 0 for the loaded-empty control and 2 for the populated control. Making every missing requested header unknown would fail the empty-list control. ### Analysis or suspected cause Source-derived: the parser starts its counter at zero and does not track parsed-package availability. The provider accepts the nonempty dump, forwards that count, and determines complete coverage from the version code or name. The listed-section limitation does not distinguish an unavailable list from a known empty inventory. MCP shares the provider path but was not executed. ### Additional context The open and closed issue and PR searches found no report covering unavailable package details and a false zero count. [PR #2359](https://github.com/morluto/rea/pull/2359) concerns restricted markers on entries in an existing permissions section. [Issue #3943](https://github.com/morluto/rea/issues/3943) concerns installer absence, and [issue #4608](https://github.com/morluto/rea/issues/4608) concerns window-focus absence. Those have different restoration checks. Normal empty-list dumps are not the failure reported here.
Author
Owner

@coygeek commented on GitHub (Oct 18, 2026):

Closing as addressed on main by 9534e42ec. Package inspection now reports an unknown requested-permission count and partial coverage for pkg=null, while preserving zero for a confirmed loaded package with an empty inventory. I verified the committed source and regression coverage; I have not rerun the runtime reproduction.

<!-- gh-comment-id:6244112956 --> @coygeek commented on GitHub (Oct 18, 2026): Closing as addressed on `main` by [9534e42ec](https://github.com/morluto/rea/commit/9534e42ec83d1004fab94574344b2a3de64744ac). Package inspection now reports an unknown requested-permission count and partial coverage for pkg=null, while preserving zero for a confirmed loaded package with an empty inventory. I verified the committed source and regression coverage; I have not rerun the runtime reproduction.
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#2426
No description provided.