fix(cli): list the read failure cause for an unreadable JSON input file
A CLI workflow given a JSON input file that cannot be read (missing, a directory, or permission denied) returned invalid_request with input_reason "read-failed" but no details. The remediation "Correct the listed arguments and retry." therefore listed nothing, and the system error that separates a typo from a permission denial was dropped. Add an invalid_value issue naming the system error code and the path. The envelope, code and input_reason are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa5750c263
commit
6020df53d9
2 files changed
+67
-3
No files matched your search
+17
-1
@@ -128,10 +128,26 @@ const jsonFileError = (
|
||||
cause === undefined ? undefined : { cause },
|
||||
reason === "invalid-json"
|
||||
? [{ path: [], reason: "invalid_format", expected: "JSON" }]
|
||||
: [],
|
||||
: [readFailureIssue(path, cause)],
|
||||
),
|
||||
),
|
||||
...(path === undefined ? {} : { input_path: path }),
|
||||
input_reason: reason,
|
||||
},
|
||||
});
|
||||
|
||||
/** Name the system error so the caller can tell a typo from a permission denial. */
|
||||
const readFailureIssue = (path: string | undefined, cause: unknown) => {
|
||||
const code =
|
||||
typeof cause === "object" &&
|
||||
cause !== null &&
|
||||
"code" in cause &&
|
||||
typeof cause.code === "string"
|
||||
? ` (${cause.code})`
|
||||
: "";
|
||||
return {
|
||||
path: [],
|
||||
reason: "invalid_value" as const,
|
||||
message: `The JSON input file could not be read${code}: ${path ?? ""}`,
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { chmod, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, onTestFinished } from "vitest";
|
||||
|
||||
import { parseCliJsonInput } from "../../../src/cliJsonInput.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
@@ -50,4 +50,52 @@ describe("CLI JSON input", () => {
|
||||
error: { input_reason: "read-failed" },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["a missing file", "ENOENT"],
|
||||
["a directory", "EISDIR"],
|
||||
] as const)(
|
||||
"lists the system cause when %s cannot be read",
|
||||
async (kind, code) => {
|
||||
const root = await createTestTempDirectory("rea-json-input-cause-");
|
||||
const path = kind === "a directory" ? root : join(root, "missing.json");
|
||||
expect(await parseCliJsonInput(path, "test-input")).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
code: "invalid_request",
|
||||
input_path: path,
|
||||
input_reason: "read-failed",
|
||||
details: {
|
||||
issues: [
|
||||
{
|
||||
path: [],
|
||||
reason: "invalid_value",
|
||||
message: `The JSON input file could not be read (${code}): ${path}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
|
||||
"lists a permission denial as the cause of an unreadable file",
|
||||
async () => {
|
||||
const root = await createTestTempDirectory("rea-json-input-denied-");
|
||||
const path = join(root, "input.json");
|
||||
await writeFile(path, "{}");
|
||||
await chmod(path, 0o000);
|
||||
onTestFinished(() => chmod(path, 0o600));
|
||||
expect(await parseCliJsonInput(path, "test-input")).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
input_reason: "read-failed",
|
||||
details: {
|
||||
issues: [{ message: expect.stringContaining("(EACCES)") }],
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user