diff --git a/packages/safe-bash/src/commands/network/output.ts b/packages/safe-bash/src/commands/network/output.ts index e9147556d..56c723c51 100644 --- a/packages/safe-bash/src/commands/network/output.ts +++ b/packages/safe-bash/src/commands/network/output.ts @@ -1,4 +1,4 @@ -import { readBytes, writeBytes, type ByteSource, type CommandContext } from "../../contracts/index.js"; +import { FsError, readBytes, writeBytes, type ByteSource, type CommandContext } from "../../contracts/index.js"; import { openFileOutput } from "../../contracts/filesystem-output.js"; import { outputFailure } from "../../contracts/io.js"; import { pathOf } from "../internal.js"; @@ -42,7 +42,7 @@ export async function writeOutput(context: CommandContext, path: string | undefi } catch (error) { signal.throwIfAborted(); if (error instanceof CurlError) throw error; - throw new CurlError(23, "Failed writing virtual output file"); + throw new CurlError(23, error instanceof FsError ? `Failed writing virtual output file: ${error.message}` : "Failed writing virtual output file"); } } diff --git a/packages/safe-bash/tests/commands/network/mounted-output.test.ts b/packages/safe-bash/tests/commands/network/mounted-output.test.ts index edf616a33..9f036aaed 100644 --- a/packages/safe-bash/tests/commands/network/mounted-output.test.ts +++ b/packages/safe-bash/tests/commands/network/mounted-output.test.ts @@ -93,7 +93,7 @@ for (const stage of ["before", "acquired", "consumed"] as const) { }); } -for (const failure of [new FsError("EACCES"), new FsError("EROFS"), new FsError("EIO"), { code: "ENOTSUP" }]) { +for (const failure of [new FsError("EACCES"), new FsError("EROFS"), new FsError("EIO"), new FsError("EACCES", { path: "/out", message: "This loaded file is read-only. Write to a new path instead.", cause: new Error("private backend detail") }), { code: "ENOTSUP" }, Object.assign(new Error("private backend detail"), { code: "EACCES" })]) { test(`curl preserves stream failure without replay: ${failure.code}`, async () => { const backing = createMemoryFileSystem(); const fs: FileSystem = { ...buffered(backing), capabilities: {} }; @@ -105,7 +105,10 @@ for (const failure of [new FsError("EACCES"), new FsError("EROFS"), new FsError( try { const result = await shell.exec("curl https://example.invalid/file -o /out"); assert.equal(result.exitCode, 23); - assert.equal(result.stderr, "curl: (23) Failed writing virtual output file\n"); + assert.equal(result.stderr, failure instanceof FsError + ? `curl: (23) Failed writing virtual output file: ${failure.message}\n` + : "curl: (23) Failed writing virtual output file\n"); + assert.doesNotMatch(result.stderr, /private backend detail/u); assert.equal(writes, 0); assert.equal(produced, 0); await assert.rejects(backing.readFile("/out"), { code: "ENOENT" }); @@ -221,7 +224,7 @@ test("curl does not retry failed buffered appends after a partial mounted write" try { const result = await shell.exec("curl https://example.invalid/file -o /scratch/out"); assert.equal(result.exitCode, 23); - assert.equal(result.stderr, "curl: (23) Failed writing virtual output file\n"); + assert.equal(result.stderr, "curl: (23) Failed writing virtual output file: EIO: input/output error, appendFile '/scratch/out'\n"); assert.equal(appended, 2); assert.equal(produced, 2); assert.deepEqual(await backing.readFile("/out"), new Uint8Array([1]));