[GH-ISSUE #7214] [Bug] provider_unavailable names not_configured providers as if they had refused the target #4305

Closed
opened 2026-10-31 00:48:43 +00:00 by bsakaguchi · 6 comments
Owner

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

When a target is admitted by no lane, provider_unavailable lists every provider
that could have taken it, and on a Windows host three of them say
not_configured (IDA, Kuna, and the 6502-only raw lane). That reads as "install
IDA" when the actual answer is "the Windows admission set is PE-only".

Repro, on Windows, with a DOS MZ:

  1. open_binary(path=...\simearth.exe) - format detection succeeds (dos-mz, x86);
  2. call any provider operation (binary_overview, search_strings, list_procedures);
  3. the error is provider_unavailable with four rejection reasons, three of them
    not_configured.

#7144 admits dos-mz in inspectWindowsP0TargetSupport, which removes this exact
case, and it explicitly leaves this wording out of scope. The remaining cases are
dos-com on Windows, and any other format the Windows set does not admit.

Ask, in order of value:

  1. When the rejection set contains no format-level refusal - every provider was
    skipped because it is unconfigured - say so, once, and name a lane that does work
    for the target on this host. For a DOS image on Windows that is the Rizin lane:
    execute_rizin_command reads the file with the MZ mapped flat, so addresses are
    file offsets. A provider that declined the format should keep saying so; the
    distinction is the whole point.
  2. Do not list not_configured providers as if they were refusals in the same
    sentence: they carry no information about the target.

The user-visible effect is a dead end that looks like a broken install: the session
opens the binary, then every follow-up fails with a message that points at software
the user never had. rea doctor already reports the Ghidra install healthy, so the
diagnosis is not where the message sends them.

Found while reconstructing SimEarth (DOS, 1990) on Windows: MzLoader + x86:LE:16:Real Mode imports and analyses it (983 functions, Ghidra 12.1.2), the decompiler is the
partial part, and the Windows admission set was the only thing in front of it.

Originally created by @pietrondo on GitHub (Oct 26, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7214 When a target is admitted by no lane, `provider_unavailable` lists every provider that could have taken it, and on a Windows host three of them say `not_configured` (IDA, Kuna, and the 6502-only raw lane). That reads as "install IDA" when the actual answer is "the Windows admission set is PE-only". Repro, on Windows, with a DOS MZ: 1. `open_binary(path=...\simearth.exe)` - format detection succeeds (`dos-mz`, x86); 2. call any provider operation (`binary_overview`, `search_strings`, `list_procedures`); 3. the error is `provider_unavailable` with four rejection reasons, three of them `not_configured`. #7144 admits `dos-mz` in `inspectWindowsP0TargetSupport`, which removes this exact case, and it explicitly leaves this wording out of scope. The remaining cases are `dos-com` on Windows, and any other format the Windows set does not admit. **Ask, in order of value:** 1. When the rejection set contains **no format-level refusal** - every provider was skipped because it is unconfigured - say so, once, and name a lane that does work for the target on this host. For a DOS image on Windows that is the Rizin lane: `execute_rizin_command` reads the file with the MZ mapped flat, so addresses are file offsets. A provider that declined the *format* should keep saying so; the distinction is the whole point. 2. Do not list `not_configured` providers as if they were refusals in the same sentence: they carry no information about the target. The user-visible effect is a dead end that looks like a broken install: the session opens the binary, then every follow-up fails with a message that points at software the user never had. `rea doctor` already reports the Ghidra install healthy, so the diagnosis is not where the message sends them. Found while reconstructing SimEarth (DOS, 1990) on Windows: MzLoader + `x86:LE:16:Real Mode` imports and analyses it (983 functions, Ghidra 12.1.2), the decompiler is the partial part, and the Windows admission set was the only thing in front of it.
Author
Owner

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

Checked the rejection path on current main at 6336fe5b.

  • candidateRejections (src/application/binary/AnalysisProviderRegistry.ts:875) flattens availability and targetSupport refusals into one array, so a provider that was never configured sits beside a provider that refused the target's format.
  • The caller-facing projection (src/domain/analysisErrorProjection.ts:445) keeps provider_id, code, reason, diagnostics only. code already separates not_configured from a format refusal, but nothing marks which entries carry no information about the target.
  • Domain-level probe on that revision (three not_configured entries plus one unsupported_target entry): userMessage is the generic "The selected analysis provider is unavailable. Review the reported local diagnostics or run rea doctor, then retry." sentence, and every rejection has the same four keys.

This is source-derived: I have no Windows host, so I did not reproduce the DOS MZ session state.

Scope I intend, unless you want it trimmed or extended:

  1. Record each rejection's origin (provider availability vs target support) as an additive field in details.rejections[].
  2. When the rejection set holds no target-origin refusal, say that once: no configured provider refused this target's format, and the format itself was not refused by any provider that is configured.
  3. Regression tests for the three states (mixed, availability-only, target-only) at the domain function and at the projection.

I am not planning to name a working lane (the second half of your first ask) in this change. Which lane works for, say, dos-com on Windows is admission knowledge that belongs with the lane definitions, and I would rather not hardcode it in the selection error. Happy to include it if you name the source of truth for that set.

Draft PR to follow.

<!-- gh-comment-id:6412168788 --> @dxqlovestudy commented on GitHub (Oct 26, 2026): Checked the rejection path on current `main` at `6336fe5b`. - `candidateRejections` (`src/application/binary/AnalysisProviderRegistry.ts:875`) flattens `availability` and `targetSupport` refusals into one array, so a provider that was never configured sits beside a provider that refused the target's format. - The caller-facing projection (`src/domain/analysisErrorProjection.ts:445`) keeps `provider_id`, `code`, `reason`, `diagnostics` only. `code` already separates `not_configured` from a format refusal, but nothing marks which entries carry no information about the target. - Domain-level probe on that revision (three `not_configured` entries plus one `unsupported_target` entry): `userMessage` is the generic "The selected analysis provider is unavailable. Review the reported local diagnostics or run `rea doctor`, then retry." sentence, and every rejection has the same four keys. This is source-derived: I have no Windows host, so I did not reproduce the DOS MZ session state. Scope I intend, unless you want it trimmed or extended: 1. Record each rejection's origin (provider availability vs target support) as an additive field in `details.rejections[]`. 2. When the rejection set holds no target-origin refusal, say that once: no configured provider refused this target's format, and the format itself was not refused by any provider that is configured. 3. Regression tests for the three states (mixed, availability-only, target-only) at the domain function and at the projection. I am not planning to name a working lane (the second half of your first ask) in this change. Which lane works for, say, `dos-com` on Windows is admission knowledge that belongs with the lane definitions, and I would rather not hardcode it in the selection error. Happy to include it if you name the source of truth for that set. Draft PR to follow.
Author
Owner

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

Source-derived against main at a7f50e338 — proposal before implementation, happy to adjust:

Today candidateRejections (src/application/binary/AnalysisProviderRegistry.ts) flattens every unavailable candidate's not_configured availability beside targetSupport refusals into one rejections list, so an unconfigured provider reads exactly like a format refusal. Proposed shape:

  1. Keep both facts, but separate them in the payload: rejections keeps target-level refusals, and a new field (e.g. unconfiguredProviders) lists providers skipped only because they are not configured — nothing is dropped, the distinction becomes visible.
  2. When rejections contains no target-level refusal, the user message says so once: no configured provider was refused by the target format; install or configure one — instead of pointing at software the user never had.
  3. "Name a lane that does work" needs a home for the format→fallback knowledge (DOS on Windows: execute_rizin_command reads the MZ flat). I'd lean toward a minimal format→direct-tool table beside the Windows admission set, one entry to start; alternatively leave it to doctor.

If the direction is agreeable I'll prepare the change with unit coverage on the win32 DOS-MZ shape.

<!-- gh-comment-id:6412246865 --> @kittimzhe commented on GitHub (Oct 26, 2026): Source-derived against main at `a7f50e338` — proposal before implementation, happy to adjust: Today `candidateRejections` (`src/application/binary/AnalysisProviderRegistry.ts`) flattens every unavailable candidate's `not_configured` availability beside targetSupport refusals into one `rejections` list, so an unconfigured provider reads exactly like a format refusal. Proposed shape: 1. Keep both facts, but separate them in the payload: `rejections` keeps target-level refusals, and a new field (e.g. `unconfiguredProviders`) lists providers skipped only because they are not configured — nothing is dropped, the distinction becomes visible. 2. When `rejections` contains no target-level refusal, the user message says so once: no configured provider was refused by the target format; install or configure one — instead of pointing at software the user never had. 3. "Name a lane that does work" needs a home for the format→fallback knowledge (DOS on Windows: `execute_rizin_command` reads the MZ flat). I'd lean toward a minimal format→direct-tool table beside the Windows admission set, one entry to start; alternatively leave it to doctor. If the direction is agreeable I'll prepare the change with unit coverage on the win32 DOS-MZ shape.
Author
Owner

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

The change is implemented locally and I will open a draft PR shortly; happy to fold in review before then.

On the payload shape, I went with one list plus an additive per-entry origin: "availability" | "target" rather than a second unconfiguredProviders array. Both answers keep every fact, but the two-array form stores the same distinction twice (once as array membership, once as the absence of a target refusal), and the consumers here already read one details.rejections[] list. The origin is tagged where the rejection is produced, so it is the observation that rejected the candidate rather than a code allowlist.

The user message follows the same rule: when every rejection is availability-origin it states that no configured provider refused the target's format; mixed and target-only sets keep the previous sentence. Nothing is dropped, and code, reason, diagnostics, candidate_ids, and selection_reason are unchanged.

On your third point (format→lane table): I left it out for the same reason as above — it is admission knowledge, and I would rather not introduce a second place that decides which lane covers a format until a maintainer names the source of truth. If that table is wanted, it reads like its own change with its own tests.

Draft PR will carry unit coverage for the availability-only, mixed, and target-only shapes; there is no Windows host here, so the DOS MZ session state itself stays unreproduced and the PR says so.

<!-- gh-comment-id:6412775360 --> @dxqlovestudy commented on GitHub (Oct 26, 2026): The change is implemented locally and I will open a draft PR shortly; happy to fold in review before then. On the payload shape, I went with one list plus an additive per-entry `origin: "availability" | "target"` rather than a second `unconfiguredProviders` array. Both answers keep every fact, but the two-array form stores the same distinction twice (once as array membership, once as the absence of a target refusal), and the consumers here already read one `details.rejections[]` list. The origin is tagged where the rejection is produced, so it is the observation that rejected the candidate rather than a code allowlist. The user message follows the same rule: when every rejection is availability-origin it states that no configured provider refused the target's format; mixed and target-only sets keep the previous sentence. Nothing is dropped, and `code`, `reason`, `diagnostics`, `candidate_ids`, and `selection_reason` are unchanged. On your third point (format→lane table): I left it out for the same reason as above — it is admission knowledge, and I would rather not introduce a second place that decides which lane covers a format until a maintainer names the source of truth. If that table is wanted, it reads like its own change with its own tests. Draft PR will carry unit coverage for the availability-only, mixed, and target-only shapes; there is no Windows host here, so the DOS MZ session state itself stays unreproduced and the PR says so.
Author
Owner

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

Fix is up as a draft: #7220.

Payload shape as described above: one details.rejections[] list, each entry tagged origin: "availability" | "target", tagged where the rejection is produced. The user message changes only when every rejection is availability-origin; mixed and target-only sets keep the previous sentence. code, reason, diagnostics, candidate_ids, and selection_reason are unchanged, and pwntools-elf now reports its configuration failure as availability.

Validation in the PR: focused 114 passed; full source suite 19117 passed / 27 skipped / 2 failed, with both failures reproduced on the base revision; check:fast, format:check, and docs:check clean. No Windows host here, so the DOS MZ session state stays unreproduced and the PR says so.

The readiness declarations are left for the human owner to complete.

<!-- gh-comment-id:6413061771 --> @dxqlovestudy commented on GitHub (Oct 26, 2026): Fix is up as a draft: #7220. Payload shape as described above: one `details.rejections[]` list, each entry tagged `origin: "availability" | "target"`, tagged where the rejection is produced. The user message changes only when every rejection is availability-origin; mixed and target-only sets keep the previous sentence. `code`, `reason`, `diagnostics`, `candidate_ids`, and `selection_reason` are unchanged, and `pwntools-elf` now reports its configuration failure as availability. Validation in the PR: focused 114 passed; full source suite 19117 passed / 27 skipped / 2 failed, with both failures reproduced on the base revision; `check:fast`, `format:check`, and `docs:check` clean. No Windows host here, so the DOS MZ session state stays unreproduced and the PR says so. The readiness declarations are left for the human owner to complete.
Author
Owner

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

Saw the above after posting my proposal — same diagnosis and shape, so deferring to your draft. For the lane-naming half, the only candidate home I could see is beside the Windows admission set in inspectWindowsP0TargetSupport, but that encodes admission rather than fallback knowledge, so deferring it as you propose reads right to me.

<!-- gh-comment-id:6413578877 --> @kittimzhe commented on GitHub (Oct 26, 2026): Saw the above after posting my proposal — same diagnosis and shape, so deferring to your draft. For the lane-naming half, the only candidate home I could see is beside the Windows admission set in `inspectWindowsP0TargetSupport`, but that encodes admission rather than fallback knowledge, so deferring it as you propose reads right to me.
Author
Owner

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

Agreed, and the distinction is the useful part: the admission set answers can this lane take the target, while the message needs what could take it on this host - a different question with a different lifetime. Folding the second into inspectWindowsP0TargetSupport would make the admission decision depend on knowledge it does not use, and it would be the first thing to rot when the lanes change.

So keeping the lane-naming out, as you suggest: the message stays on the rejection path, and if it grows a lane name it reads it from a small table next to the provider list rather than from the admission function. For a DOS image on Windows that table says Rizin, and nothing in the admission path has to know it.

Thanks for the read - it settles the shape of the change I will keep in #7214.

<!-- gh-comment-id:6418682429 --> @pietrondo commented on GitHub (Oct 26, 2026): Agreed, and the distinction is the useful part: the admission set answers *can this lane take the target*, while the message needs *what could take it on this host* - a different question with a different lifetime. Folding the second into `inspectWindowsP0TargetSupport` would make the admission decision depend on knowledge it does not use, and it would be the first thing to rot when the lanes change. So keeping the lane-naming out, as you suggest: the message stays on the rejection path, and if it grows a lane name it reads it from a small table next to the provider list rather than from the admission function. For a DOS image on Windows that table says Rizin, and nothing in the admission path has to know it. Thanks for the read - it settles the shape of the change I will keep in #7214.
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#4305
No description provided.