fix(signals): name the limiter as a possible cause of a failed sigrok version probe (#6395)

* fix(signals): report a failed sigrok version probe as a configuration error

* fix(signals): attribute a failed version probe to the limited probe

* fix(signals): name the limiter as a possible cause of a failed version probe

The version probe runs under the resource limiter, so a nonzero status
can come from prlimit as well as sigrok-cli. Say so in the configuration
refusal instead of blaming sigrok-cli alone, and cover a limiter that
refuses to start the tool for both decode and discovery.

* test(signals): assert limiter probe failures by typed fields

The limiter-refused recording test locked English probe wording. Assert the capability-unavailable code, provider, operation, and captured limiter output instead.

* fix(signals): record an unattributed version-probe status owner

A nonzero resource-limited version probe now reports version_probe_status_owner limiter-or-tool. The limiter-refusal test requires that field, so the old sigrok-cli-only failure no longer passes.

---------

Co-authored-by: Abelo9996 <Abelo9996@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-11-06 01:46:40 +08:00
1 parent 560674e2de
commit c1ee8289c9
2 files changed
+73 -5

No files matched your search

+24 -3
View File
@@ -76,6 +76,21 @@ type Launcher = NonNullable<Parameters<typeof runOwnedCommand>[2]>["launcher"];
export const sigrokHostSupported = (platform: NodeJS.Platform): boolean =>
platform === "linux";
/**
* A nonzero version probe ran under the resource limiter, so the status is not
* attributed to sigrok-cli alone.
*/
class ResourceLimitedVersionProbeFailure extends Error {
readonly statusOwner = "limiter-or-tool" as const;
constructor() {
super(
"The resource-limited sigrok-cli --version probe failed; the status may come from the limiter or from sigrok-cli, so inspect both selected installations and the captured output.",
);
this.name = "ResourceLimitedVersionProbeFailure";
}
}
/** Decode selected recordings only, retaining cleanup ownership across failures. */
export class SigrokRecordingProvider implements SignalRecordingPort {
readonly #resources: ArtifactAnalysisResources;
@@ -270,10 +285,9 @@ export class SigrokRecordingProvider implements SignalRecordingPort {
{ capturedOutput, userMessage: rejection.message },
[rejection],
);
// The probe runs under the limiter, so either program may own the status.
if (output.exitCode !== 0 && phase === "configuration")
throw new Error(
"The selected sigrok-cli installation failed its --version probe; inspect its captured output.",
);
throw new ResourceLimitedVersionProbeFailure();
if (output.exitCode !== 0)
throw new ProviderAdapterError(
SIGROK_PROVIDER_IDENTITY.id,
@@ -528,6 +542,13 @@ export class SigrokRecordingProvider implements SignalRecordingPort {
...(observedOutput === undefined
? {}
: { capturedOutput: observedOutput }),
...(cause instanceof ResourceLimitedVersionProbeFailure
? {
diagnostics: {
version_probe_status_owner: cause.statusOwner,
},
}
: {}),
userMessage: `${reason} On Linux, configure sigrok-cli 0.7.2 through REA_SIGROK_COMMAND and util-linux prlimit through REA_SIGROK_PRLIMIT_COMMAND (default /usr/bin/prlimit).`,
},
);
@@ -95,6 +95,15 @@ const fixture = async (
const configuredCommand =
mode === "symlink" ? join(directory, "configured-tool") : script;
if (mode === "symlink") await symlink(script, configuredCommand);
// A separate limiter that refuses to start the wrapped tool.
const limiter = join(directory, "limiter.mjs");
if (mode === "limiter-refused") {
await writeFile(
limiter,
"console.error('prlimit: failed to execute sigrok-cli'); process.exitCode = 1;\n",
);
await chmod(limiter, 0o700);
}
const controller = new AbortController();
let calls = 0;
let mayClean = !failCleanup;
@@ -104,7 +113,8 @@ const fixture = async (
{
...process.env,
REA_SIGROK_COMMAND: configuredCommand,
REA_SIGROK_PRLIMIT_COMMAND: configuredCommand,
REA_SIGROK_PRLIMIT_COMMAND:
mode === "limiter-refused" ? limiter : configuredCommand,
},
async (input) => {
calls++;
@@ -127,7 +137,10 @@ const fixture = async (
const spawned = await spawnOwnedProviderProcess({
...input,
command: process.execPath,
arguments: [script, ...input.arguments],
arguments: [
mode === "limiter-refused" ? limiter : script,
...input.arguments,
],
expectedCommand: process.execPath,
});
if (mode === "wait" && !input.arguments.includes("--version"))
@@ -1415,3 +1428,37 @@ it("classifies completed nonzero version probes as configuration failures for bo
}
expect(f.calls()).toBe(2);
});
it("does not blame sigrok-cli alone when the limiter fails the version probe", async () => {
const f = await fixture("limiter-refused");
for (const [operation, task] of [
[
"decode_signal_recording",
() => f.service.decode({ path: f.path, decoders: "uart" }),
],
["inspect_signal_decoders", () => f.service.inspectDecoders({})],
] as const) {
const result = await task();
if (result.ok) throw new Error("Expected version admission refusal");
expect(result.error).toMatchObject({
_tag: "AnalysisCapabilityUnavailableError",
providerId: "sigrok-recording",
operation,
});
expect(projectAnalysisError(result.error)).toMatchObject({
code: "capability_unavailable",
details: {
provider_id: "sigrok-recording",
operation,
// Absent when the probe is reported as a sigrok-cli failure alone.
diagnostics: { version_probe_status_owner: "limiter-or-tool" },
captured_output: {
exit_code: 1,
stdout: "",
stderr: "prlimit: failed to execute sigrok-cli\n",
},
},
});
}
expect(f.calls()).toBe(2);
});