[GH-ISSUE #3369] [Bug] ADB push reports checksum capture overflow as device unavailability #1235

Closed
opened 2026-10-16 21:03:08 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

push-adb-file returns successful Evidence with device_digest_source: "unavailable" when a valid sha256sum response exceeds the operation's 1 KiB capture budget. A normal checksum line for a 1,142-character absolute device path triggers this behavior. The producer returned the correct checksum, but the result discards the resource failure and says the device did not answer. Callers lose the reason verification failed and receive the same outcome as a genuinely missing checksum command.

All three requests in the local CLI reproduction exited with status 0. The long-path response contained device_sha256: null, device_digest_source: "unavailable", and no resource error. Its limitation said, "The device did not answer sha256sum, so the transfer is not device-side verified". The short-path control retained the correct checksum; the genuinely missing command produced the same digest status as the long-path failure.

The long-path producer writes a 1,209-byte normal checksum line. The copy and checksum are local fixture observations; no live Android transfer was exercised.

Reproduction

  1. Start from a clean checkout of morluto/rea and select the baseline with git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063.
  2. From the repository root, run npm ci and npm run build:cached. These are clean-checkout prerequisites; the observed reproduction used installed dependencies and a built runtime.
  3. Run the following exact script from the repository root. It creates every input file, models the fixed adb calls, copies the pushed bytes into a local file, and computes a real checksum over that copy. It never contacts a device. Every component of the long device path is short, and the full path is below 4,096 characters.
work=$(mktemp -d)
mkdir -p "$work/home"
printf 'file bytes' > "$work/sample.bin"
cat > "$work/adb" <<'JS'
#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const crypto = require('node:crypto');
let args = process.argv.slice(2);
if (args[0] === 'version') {
  console.log('Android Debug Bridge version 1.0.41\nVersion 34.0.5-fixture\nInstalled as ' + __filename);
  process.exit(0);
}
if (args[0] === '-s') args = args.slice(2);
if (args[0] === 'shell' && args[1] === 'test') process.exit(1);
const copied = path.join(path.dirname(__filename), 'copied.bin');
if (args[0] === 'push') {
  fs.copyFileSync(args[1], copied);
  console.log('1 file pushed');
} else if (args[0] === 'shell' && args[1] === 'sha256sum') {
  if (process.env.DIGEST_MODE === 'missing') {
    console.error('sha256sum unavailable');
    process.exit(127);
  }
  const hash = crypto.createHash('sha256').update(fs.readFileSync(copied)).digest('hex');
  process.stdout.write(hash + '  ' + args[2] + '\n');
} else process.exit(90);
JS
chmod +x "$work/adb"
long_device_path=$(node -e 'process.stdout.write("/data/local/" + "nested/".repeat(160) + "sample.bin")')
env HOME="$work/home" REA_ADB_PATH="$work/adb" \
  node scripts/rea.mjs push-adb-file probe-device "$work/sample.bin" "$long_device_path" --format json > "$work/long.json"
env HOME="$work/home" REA_ADB_PATH="$work/adb" \
  node scripts/rea.mjs push-adb-file probe-device "$work/sample.bin" /data/local/sample.bin --format json > "$work/short.json"
env HOME="$work/home" REA_ADB_PATH="$work/adb" DIGEST_MODE=missing \
  node scripts/rea.mjs push-adb-file probe-device "$work/sample.bin" /data/local/sample.bin --format json > "$work/missing.json"
node - "$work" <<'JS'
const fs = require('node:fs');
const root = process.argv[2];
for (const name of ['long', 'short', 'missing']) {
  const evidence = JSON.parse(fs.readFileSync(root + '/' + name + '.json', 'utf8'));
  const result = evidence.normalized_result;
  console.log(JSON.stringify({
    case: name,
    device_path_length: result.device_path.length,
    device_sha256: result.device_sha256,
    device_digest_source: result.device_digest_source,
    code: evidence.code ?? null
  }));
}
JS
rm -rf "$work"
  1. Compare the three printed results with the evidence below. The long-path result loses verification despite an available correct checksum, the short-path control verifies successfully, and the missing-command control remains unverified.

Expected behavior

An available checksum command returning the correct digest should verify the transfer for the caller-selected path, as documented in docs/adb-device-analysis.md:142. If a capture budget prevents interpreting the response, the caller must receive the documented resource_constraint outcome from docs/adb-device-analysis.md:153, with the completed push preserved as an observation where appropriate. A capture failure must remain distinguishable from an unavailable checksum command.

Impact and scope

Impact level: P2, supported. The public CLI silently discards the checksum capture failure, loses otherwise available verification, and attributes the failure to device unavailability. The demonstrated input is a long absolute path with short components; live-device frequency was not measured.

Workaround status: available. Run the device's sha256sum through adb directly and retain the complete response separately. A shorter selected destination also works when the caller can change it; the short-path control directly verified the matching checksum.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64. The adb identity comes from the inline local fixture, not an installed Android platform-tools binary.

Target and artifact context

Local sample.bin containing file bytes; synthetic adb executable reporting Android Debug Bridge 1.0.41 and version 34.0.5-fixture; selected serial probe-device; isolated home directory; long destination of 1,142 characters and short destination /data/local/sample.bin of 22 characters. The fixture copies the file locally and computes its real SHA-256 checksum. No live Android device, emulator, or transfer was exercised.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

{"case":"long","device_path_length":1142,"device_sha256":null,"device_digest_source":"unavailable","code":null}
{"case":"short","device_path_length":22,"device_sha256":"4f85ab790f08374b7408ce7463ee3fe0d20fc8f7ab2a5df649f4dc53386eabc4","device_digest_source":"device_sha256sum","code":null}
{"case":"missing","device_path_length":22,"device_sha256":null,"device_digest_source":"unavailable","code":null}

These are the observed results from the executed CLI reproduction. The long-path result also states, "The device did not answer sha256sum, so the transfer is not device-side verified". All three requests exited with status 0.

Typed outcome or recovery expected

Repeat the long-path request and confirm that it retains the matching device checksum when the response is admitted, or reports the capture constraint explicitly instead of claiming the command was unavailable. Keep the short-path control verified and the missing-command control explicitly unverified. Also check that a genuinely excessive digest response retains its resource failure and does not disappear into the optional-command fallback.

Analysis or suspected cause

Source-derived reasoning: src/adb/AdbProvider.ts:1760 captures the digest with a 1,024-byte maximum, src/adb/AdbProvider.ts:184 classifies exceeded capture as a resource constraint, and src/adb/AdbProvider.ts:1798 converts every failed digest request to successful unverified Evidence. This explains why a valid 1,209-byte checksum line loses the resource failure and receives the same fallback as an unavailable checksum command. The primary evidence is the executed long-path CLI result and the short-path and missing-command controls; source inspection explains the result.

Additional context

  • #2602 corrected the push-file description's false exclusivity claim; it concerned advertised effects wording rather than checksum capture or verification outcomes.
  • #3166 concerns installation request metadata reported as an observed replacement; this concerns push-file checksum failure classification.
  • #2175 concerns incorrect logcat byte counts for non-ASCII text; this concerns discarded checksum capture failures and unavailable-command attribution.
  • #1952 concerns provider configuration refusal errors; this uses an admitted executable and reaches the checksum request after a successful push.
  • #3337 concerns Apktool launcher ownership and cleanup after overflow; this concerns ADB's optional-checksum fallback and does not establish a surviving-process defect.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3369 ### 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 Evidence, artifacts, or reconstruction ### Observed behavior `push-adb-file` returns successful Evidence with `device_digest_source: "unavailable"` when a valid `sha256sum` response exceeds the operation's 1 KiB capture budget. A normal checksum line for a 1,142-character absolute device path triggers this behavior. The producer returned the correct checksum, but the result discards the resource failure and says the device did not answer. Callers lose the reason verification failed and receive the same outcome as a genuinely missing checksum command. All three requests in the local CLI reproduction exited with status 0. The long-path response contained `device_sha256: null`, `device_digest_source: "unavailable"`, and no resource error. Its limitation said, "The device did not answer sha256sum, so the transfer is not device-side verified". The short-path control retained the correct checksum; the genuinely missing command produced the same digest status as the long-path failure. The long-path producer writes a 1,209-byte normal checksum line. The copy and checksum are local fixture observations; no live Android transfer was exercised. ### Reproduction 1. Start from a clean checkout of `morluto/rea` and select the baseline with `git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063`. 2. From the repository root, run `npm ci` and `npm run build:cached`. These are clean-checkout prerequisites; the observed reproduction used installed dependencies and a built runtime. 3. Run the following exact script from the repository root. It creates every input file, models the fixed adb calls, copies the pushed bytes into a local file, and computes a real checksum over that copy. It never contacts a device. Every component of the long device path is short, and the full path is below 4,096 characters. ```sh work=$(mktemp -d) mkdir -p "$work/home" printf 'file bytes' > "$work/sample.bin" cat > "$work/adb" <<'JS' #!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); const crypto = require('node:crypto'); let args = process.argv.slice(2); if (args[0] === 'version') { console.log('Android Debug Bridge version 1.0.41\nVersion 34.0.5-fixture\nInstalled as ' + __filename); process.exit(0); } if (args[0] === '-s') args = args.slice(2); if (args[0] === 'shell' && args[1] === 'test') process.exit(1); const copied = path.join(path.dirname(__filename), 'copied.bin'); if (args[0] === 'push') { fs.copyFileSync(args[1], copied); console.log('1 file pushed'); } else if (args[0] === 'shell' && args[1] === 'sha256sum') { if (process.env.DIGEST_MODE === 'missing') { console.error('sha256sum unavailable'); process.exit(127); } const hash = crypto.createHash('sha256').update(fs.readFileSync(copied)).digest('hex'); process.stdout.write(hash + ' ' + args[2] + '\n'); } else process.exit(90); JS chmod +x "$work/adb" long_device_path=$(node -e 'process.stdout.write("/data/local/" + "nested/".repeat(160) + "sample.bin")') env HOME="$work/home" REA_ADB_PATH="$work/adb" \ node scripts/rea.mjs push-adb-file probe-device "$work/sample.bin" "$long_device_path" --format json > "$work/long.json" env HOME="$work/home" REA_ADB_PATH="$work/adb" \ node scripts/rea.mjs push-adb-file probe-device "$work/sample.bin" /data/local/sample.bin --format json > "$work/short.json" env HOME="$work/home" REA_ADB_PATH="$work/adb" DIGEST_MODE=missing \ node scripts/rea.mjs push-adb-file probe-device "$work/sample.bin" /data/local/sample.bin --format json > "$work/missing.json" node - "$work" <<'JS' const fs = require('node:fs'); const root = process.argv[2]; for (const name of ['long', 'short', 'missing']) { const evidence = JSON.parse(fs.readFileSync(root + '/' + name + '.json', 'utf8')); const result = evidence.normalized_result; console.log(JSON.stringify({ case: name, device_path_length: result.device_path.length, device_sha256: result.device_sha256, device_digest_source: result.device_digest_source, code: evidence.code ?? null })); } JS rm -rf "$work" ``` 4. Compare the three printed results with the evidence below. The long-path result loses verification despite an available correct checksum, the short-path control verifies successfully, and the missing-command control remains unverified. ### Expected behavior An available checksum command returning the correct digest should verify the transfer for the caller-selected path, as documented in `docs/adb-device-analysis.md:142`. If a capture budget prevents interpreting the response, the caller must receive the documented `resource_constraint` outcome from `docs/adb-device-analysis.md:153`, with the completed push preserved as an observation where appropriate. A capture failure must remain distinguishable from an unavailable checksum command. ### Impact and scope Impact level: P2, supported. The public CLI silently discards the checksum capture failure, loses otherwise available verification, and attributes the failure to device unavailability. The demonstrated input is a long absolute path with short components; live-device frequency was not measured. Workaround status: available. Run the device's `sha256sum` through adb directly and retain the complete response separately. A shorter selected destination also works when the caller can change it; the short-path control directly verified the matching checksum. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64. The adb identity comes from the inline local fixture, not an installed Android platform-tools binary. ### Target and artifact context Local `sample.bin` containing `file bytes`; synthetic adb executable reporting Android Debug Bridge 1.0.41 and version 34.0.5-fixture; selected serial `probe-device`; isolated home directory; long destination of 1,142 characters and short destination `/data/local/sample.bin` of 22 characters. The fixture copies the file locally and computes its real SHA-256 checksum. No live Android device, emulator, or transfer was exercised. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence ```text {"case":"long","device_path_length":1142,"device_sha256":null,"device_digest_source":"unavailable","code":null} {"case":"short","device_path_length":22,"device_sha256":"4f85ab790f08374b7408ce7463ee3fe0d20fc8f7ab2a5df649f4dc53386eabc4","device_digest_source":"device_sha256sum","code":null} {"case":"missing","device_path_length":22,"device_sha256":null,"device_digest_source":"unavailable","code":null} ``` These are the observed results from the executed CLI reproduction. The long-path result also states, "The device did not answer sha256sum, so the transfer is not device-side verified". All three requests exited with status 0. ### Typed outcome or recovery expected Repeat the long-path request and confirm that it retains the matching device checksum when the response is admitted, or reports the capture constraint explicitly instead of claiming the command was unavailable. Keep the short-path control verified and the missing-command control explicitly unverified. Also check that a genuinely excessive digest response retains its resource failure and does not disappear into the optional-command fallback. ### Analysis or suspected cause Source-derived reasoning: `src/adb/AdbProvider.ts:1760` captures the digest with a 1,024-byte maximum, `src/adb/AdbProvider.ts:184` classifies exceeded capture as a resource constraint, and `src/adb/AdbProvider.ts:1798` converts every failed digest request to successful unverified Evidence. This explains why a valid 1,209-byte checksum line loses the resource failure and receives the same fallback as an unavailable checksum command. The primary evidence is the executed long-path CLI result and the short-path and missing-command controls; source inspection explains the result. ### Additional context - [#2602](https://github.com/morluto/rea/issues/2602) corrected the push-file description's false exclusivity claim; it concerned advertised effects wording rather than checksum capture or verification outcomes. - [#3166](https://github.com/morluto/rea/issues/3166) concerns installation request metadata reported as an observed replacement; this concerns push-file checksum failure classification. - [#2175](https://github.com/morluto/rea/issues/2175) concerns incorrect logcat byte counts for non-ASCII text; this concerns discarded checksum capture failures and unavailable-command attribution. - [#1952](https://github.com/morluto/rea/issues/1952) concerns provider configuration refusal errors; this uses an admitted executable and reaches the checksum request after a successful push. - [#3337](https://github.com/morluto/rea/issues/3337) concerns Apktool launcher ownership and cleanup after overflow; this concerns ADB's optional-checksum fallback and does not establish a surviving-process defect.
Author
Owner

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

Checked the reported fix on main at d87e73f2db.

After a completed push, a checksum resource limit or timeout now remains a typed error (as does cancellation), with a partial observation recording the completed push and unverified device digest. Only genuinely unavailable/failed checksum cases retain successful unverified Evidence fallback. The regression overflows the digest capture and asserts resource_constraint attributed to push_adb_file plus partialObservation; this exercises the original classification contract though its oversized response is generic fixture bytes rather than the report's normal long-path line.

Implementation: AdbProvider.ts, AdbProvider.ts.

The relevant regression checks in AdbProvider.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380762150 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. After a completed push, a checksum resource limit or timeout now remains a typed error (as does cancellation), with a partial observation recording the completed push and unverified device digest. Only genuinely unavailable/failed checksum cases retain successful unverified Evidence fallback. The regression overflows the digest capture and asserts resource_constraint attributed to push_adb_file plus partialObservation; this exercises the original classification contract though its oversized response is generic fixture bytes rather than the report's normal long-path line. Implementation: [AdbProvider.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/adb/AdbProvider.ts#L2575), [AdbProvider.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/adb/AdbProvider.ts#L2591). The relevant regression checks in [AdbProvider.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/adb/AdbProvider.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#1235
No description provided.