[GH-ISSUE #7182] [Bug] Hopper Linux setup drops HTTP failure status and gives the same recovery message for 404 and 503 #4295

Closed
opened 2026-10-31 00:48:37 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @WenyuChiou on GitHub (Oct 26, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7182

Before submitting

  • Searched open and closed issues and related PRs.
  • No credentials, private paths, or target data are included.

Area / interface / earliest stage

Setup, packaging, or upgrade / Package or setup / Setup or provider admission.

Observed behavior

An offline reproduction using the production downloader, Linux installer and setup failure mapper returns the same result for synthetic HTTP 404 and 503 responses:

{"status":"failed","reason":"download"}
{"status":"failed","code":"download_failed","remediation":"REA could not download or unpack the official Hopper package. Check network and filesystem access, then retry setup."}

The HTTP status is discarded, so the diagnostic cannot distinguish the observed HTTP 404 response from HTTP 503. This reports diagnostic information loss, not an observed outage of Hopper's service.

Reproduction

The executed files came from an older VM source snapshot without Git metadata, not a verified checkout of current main. Before testing, their Git blob hashes were compared with main commit 1173c0a9dc1b352a60943a491462294db8f353ae and matched exactly:

  • src/application/HopperDownload.ts: c9cb542c88b97ddd2ed9b7e5af12a6be5a381cf6
  • src/application/LinuxHopper.ts: 871b5be48f4d9299349dce88197068051efceb0b
  • src/application/SetupInstallFailure.ts: 6610e9f66168bce1401de82617046d20412cd2b5

This establishes main-blob correspondence for those three executed modules, not the entire snapshot or dependency graph. The reproduction command below is expressed relative to a checkout root.
Executed on Linux x64, Node.js v24.22.1, using the checkout's existing tsx dependency.

From that checkout with dependencies already available, run the command below. It uses a synthetic release entry and replaces fetch with in-memory responses. The proxy throws if the installer attempts any unprovided host operation. No network request, file staging, package installation, or provider execution occurs.

node --import tsx --input-type=module <<'EOF'
import assert from 'node:assert/strict';
import { downloadHopperPackage } from './src/application/HopperDownload.ts';
import { installLinuxHopper } from './src/application/LinuxHopper.ts';
import { setupInstallFailure } from './src/application/SetupInstallFailure.ts';
const original = globalThis.fetch;
try {
 for (const status of [404,503]) {
  let fetchCalls=0;
  globalThis.fetch=async ()=>{fetchCalls++;return new Response(null,{status});};
  const host = new Proxy({
   distribution:async()=>({id:'ubuntu',versionId:'24.04',packageFamily:'deb',hopperSetupSupported:true}),
   release:async()=>({ok:true,value:{filename:'https://vendor.example/package.deb'}}),
   download:(url,options)=>downloadHopperPackage(url,{...options,timeoutMs:1000}),
  },{get(target,key){if(key in target)return target[key];throw new Error(`Unexpected host access: ${String(key)}`);}});
  const result=await installLinuxHopper(host);
  assert.deepEqual(result,{status:'failed',reason:'download'});
  assert.equal(fetchCalls,1);
  const projected=setupInstallFailure(result.reason,result.packageFailure,'linux');
  assert.equal(projected.code,'download_failed');
  assert.equal(/404|503/.test(JSON.stringify(projected)),false);
  console.log(JSON.stringify({synthetic_http_status:status,installer:result,failure_mapping:projected}));
 }
} finally { globalThis.fetch=original; }
EOF

Both statuses produced the output above. Each had exactly one fake fetch call. The assertions describe the current behavior; they pass while the information loss exists.

Expected behavior

Keep the HTTP failure status through the download and installation failure projection so the setup diagnostic can report, for example, HTTP 404 versus HTTP 503. An existing top-level download_failed code can remain; the underlying HTTP fact should not be lost.

This follows the repository guidance to preserve meaningful failure reasons. It does not assume that a particular status proves its root cause or that a retry will succeed. No automatic retry is requested.

Impact and verification scope

Observed: the production-function reproduction loses the HTTP status for both cases.

Source-derived: the Linux setup adapter has no HTTP detail to append for this result and therefore returns the generic remediation. The full CLI/setup adapter was not executed.

Not tested: real vendor responses, actual installation, macOS, other HTTP failures, or provider behavior. Real-world frequency and a regression window are NOT_ENOUGH_INFO. No claim that setup reports success; it correctly reports failure, but without the observed HTTP status.

Source evidence

All links refer to the main commit used for source comparison:

Relation to existing work and proposed scope

Related: #7077 and #7150. At review time, #7150 is open at e3b3e5e6647f82d60f5809df409aff095cf778c3. Its “Intentionally not included” section explicitly leaves HTTP-status diagnostics out:

Tagged reasons for the other { ok: false } refusals

It includes HTTP status among those cases and states:

That is a separate follow-up.

This report proposes only that ordinary HTTP diagnostic follow-up, not a replacement for the timeout PR. Scope: preserve the status from a failed HTTP response and expose it in setup diagnostics, with offline 404/503 regression coverage across producer and consumer. No change to download timing, retries, redirects, response-size limits, or package verification. No maintainer agreement or implementation ownership is implied.

Originally created by @WenyuChiou on GitHub (Oct 26, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7182 ### Before submitting - [x] Searched open and closed issues and related PRs. - [x] No credentials, private paths, or target data are included. ### Area / interface / earliest stage Setup, packaging, or upgrade / Package or setup / Setup or provider admission. ### Observed behavior An offline reproduction using the production downloader, Linux installer and setup failure mapper returns the same result for synthetic HTTP 404 and 503 responses: ```json {"status":"failed","reason":"download"} {"status":"failed","code":"download_failed","remediation":"REA could not download or unpack the official Hopper package. Check network and filesystem access, then retry setup."} ``` The HTTP status is discarded, so the diagnostic cannot distinguish the observed HTTP 404 response from HTTP 503. This reports diagnostic information loss, not an observed outage of Hopper's service. ### Reproduction The executed files came from an older VM source snapshot without Git metadata, not a verified checkout of current main. Before testing, their Git blob hashes were compared with main commit `1173c0a9dc1b352a60943a491462294db8f353ae` and matched exactly: - `src/application/HopperDownload.ts`: `c9cb542c88b97ddd2ed9b7e5af12a6be5a381cf6` - `src/application/LinuxHopper.ts`: `871b5be48f4d9299349dce88197068051efceb0b` - `src/application/SetupInstallFailure.ts`: `6610e9f66168bce1401de82617046d20412cd2b5` This establishes main-blob correspondence for those three executed modules, not the entire snapshot or dependency graph. The reproduction command below is expressed relative to a checkout root. Executed on Linux x64, Node.js v24.22.1, using the checkout's existing tsx dependency. From that checkout with dependencies already available, run the command below. It uses a synthetic release entry and replaces fetch with in-memory responses. The proxy throws if the installer attempts any unprovided host operation. No network request, file staging, package installation, or provider execution occurs. ```sh node --import tsx --input-type=module <<'EOF' import assert from 'node:assert/strict'; import { downloadHopperPackage } from './src/application/HopperDownload.ts'; import { installLinuxHopper } from './src/application/LinuxHopper.ts'; import { setupInstallFailure } from './src/application/SetupInstallFailure.ts'; const original = globalThis.fetch; try { for (const status of [404,503]) { let fetchCalls=0; globalThis.fetch=async ()=>{fetchCalls++;return new Response(null,{status});}; const host = new Proxy({ distribution:async()=>({id:'ubuntu',versionId:'24.04',packageFamily:'deb',hopperSetupSupported:true}), release:async()=>({ok:true,value:{filename:'https://vendor.example/package.deb'}}), download:(url,options)=>downloadHopperPackage(url,{...options,timeoutMs:1000}), },{get(target,key){if(key in target)return target[key];throw new Error(`Unexpected host access: ${String(key)}`);}}); const result=await installLinuxHopper(host); assert.deepEqual(result,{status:'failed',reason:'download'}); assert.equal(fetchCalls,1); const projected=setupInstallFailure(result.reason,result.packageFailure,'linux'); assert.equal(projected.code,'download_failed'); assert.equal(/404|503/.test(JSON.stringify(projected)),false); console.log(JSON.stringify({synthetic_http_status:status,installer:result,failure_mapping:projected})); } } finally { globalThis.fetch=original; } EOF ``` Both statuses produced the output above. Each had exactly one fake fetch call. The assertions describe the current behavior; they pass while the information loss exists. ### Expected behavior Keep the HTTP failure status through the download and installation failure projection so the setup diagnostic can report, for example, HTTP 404 versus HTTP 503. An existing top-level `download_failed` code can remain; the underlying HTTP fact should not be lost. This follows the [repository guidance to preserve meaningful failure reasons](https://github.com/morluto/rea/blob/1173c0a9dc1b352a60943a491462294db8f353ae/AGENTS.md#L33). It does not assume that a particular status proves its root cause or that a retry will succeed. No automatic retry is requested. ### Impact and verification scope Observed: the production-function reproduction loses the HTTP status for both cases. Source-derived: the Linux setup adapter has no HTTP detail to append for this result and therefore returns the generic remediation. The full CLI/setup adapter was not executed. Not tested: real vendor responses, actual installation, macOS, other HTTP failures, or provider behavior. Real-world frequency and a regression window are NOT_ENOUGH_INFO. No claim that setup reports success; it correctly reports failure, but without the observed HTTP status. ### Source evidence All links refer to the main commit used for source comparison: - [Downloader discards non-success HTTP status](https://github.com/morluto/rea/blob/1173c0a9dc1b352a60943a491462294db8f353ae/src/application/HopperDownload.ts#L29-L39). - [Linux installer reduces the failed download to reason download](https://github.com/morluto/rea/blob/1173c0a9dc1b352a60943a491462294db8f353ae/src/application/LinuxHopper.ts#L401-L404). - [Failure mapper returns generic remediation](https://github.com/morluto/rea/blob/1173c0a9dc1b352a60943a491462294db8f353ae/src/application/SetupInstallFailure.ts#L233-L238). - [Setup adapter appends only available failure details](https://github.com/morluto/rea/blob/1173c0a9dc1b352a60943a491462294db8f353ae/src/application/SetupHost.ts#L267-L291). ### Relation to existing work and proposed scope Related: [#7077](https://github.com/morluto/rea/issues/7077) and [#7150](https://github.com/morluto/rea/pull/7150). At review time, #7150 is open at `e3b3e5e6647f82d60f5809df409aff095cf778c3`. Its “Intentionally not included” section explicitly leaves HTTP-status diagnostics out: > Tagged reasons for the other `{ ok: false }` refusals It includes HTTP status among those cases and states: > That is a separate follow-up. This report proposes only that ordinary HTTP diagnostic follow-up, not a replacement for the timeout PR. Scope: preserve the status from a failed HTTP response and expose it in setup diagnostics, with offline 404/503 regression coverage across producer and consumer. No change to download timing, retries, redirects, response-size limits, or package verification. No maintainer agreement or implementation ownership is implied.
Author
Owner

@XinwangTao commented on GitHub (Oct 26, 2026):

Good catch — 404 and 503 call for very different user actions (404 usually means the release entry/URL is stale or the artifact was removed, while 503 points to a transient vendor outage where a later retry is reasonable), so collapsing both into one generic remediation makes setup failures much harder to triage. Keeping the raw HTTP status in the failure projection as #7183 does is the right minimal fix, and the offline 404/503 regression coverage is a nice touch. One suggestion: also surface the HTTP status in the remediation text itself (e.g. "HTTP 404") rather than only in internal diagnostics, since that string is what end users paste into issue reports.

<!-- gh-comment-id:6406104693 --> @XinwangTao commented on GitHub (Oct 26, 2026): Good catch — 404 and 503 call for very different user actions (404 usually means the release entry/URL is stale or the artifact was removed, while 503 points to a transient vendor outage where a later retry is reasonable), so collapsing both into one generic remediation makes setup failures much harder to triage. Keeping the raw HTTP status in the failure projection as #7183 does is the right minimal fix, and the offline 404/503 regression coverage is a nice touch. One suggestion: also surface the HTTP status in the remediation text itself (e.g. "HTTP 404") rather than only in internal diagnostics, since that string is what end users paste into issue reports.
Author
Owner

@WenyuChiou commented on GitHub (Oct 26, 2026):

Thanks, @XinwangTao. Agreed that the status should appear in the user-facing remediation. #7183 already proposes that: download: Package download returned HTTP 404. … (or HTTP 503), while keeping download_failed unchanged.

The 404/503 regression test checks that exact remediation through the production setup adapter. It uses synthetic responses, not a real vendor download. The PR is not merged yet, and the status alone does not establish the underlying cause or guarantee that retrying will succeed.

<!-- gh-comment-id:6406214675 --> @WenyuChiou commented on GitHub (Oct 26, 2026): Thanks, @XinwangTao. Agreed that the status should appear in the user-facing remediation. #7183 already proposes that: `download: Package download returned HTTP 404. …` (or `HTTP 503`), while keeping `download_failed` unchanged. The [404/503 regression test](https://github.com/morluto/rea/blob/701bf8aff9e19ebd3158ba8923c4caf41d7363c8/tests/process-global/hopperDownloadHttpFailure.test.ts#L75-L98) checks that exact remediation through the production setup adapter. It uses synthetic responses, not a real vendor download. The PR is not merged yet, and the status alone does not establish the underlying cause or guarantee that retrying will succeed.
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#4295
No description provided.