From 2de08c3df3946cbf84161ed336149d10d63a71ef Mon Sep 17 00:00:00 2001 From: tdurieux Date: Thu, 7 May 2026 07:47:29 +0300 Subject: [PATCH] Add missing error handlers on stream pipelines - AnonymizedFile.anonymizedContent(): propagate content errors to the anonymizer so callers see the failure instead of hanging. - AnonymizedFile.send() local path: add error handler on the anonymizer transform between content and response pipes. - S3.send(): handle errors on the S3 body stream to avoid unhandled emits crashing the process. - S3.archive() / FileSystem.archive(): propagate read-stream errors to the file transformer so archiver sees the failure. - Add frontend translations for new error codes. --- public/i18n/locale-en.json | 4 +++- src/core/AnonymizedFile.ts | 2 ++ src/core/storage/FileSystem.ts | 8 +++++--- src/core/storage/S3.ts | 16 ++++++++++++---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/public/i18n/locale-en.json b/public/i18n/locale-en.json index 671dfd4..90947e8 100644 --- a/public/i18n/locale-en.json +++ b/public/i18n/locale-en.json @@ -103,7 +103,9 @@ "username_not_defined": "A username must be provided.", "github_user_not_found": "The specified GitHub user could not be found.", "cannot_coauthor_self": "You cannot add yourself as a co-author.", - "storage_write_size_mismatch": "The downloaded file was smaller than expected. The upstream source may have returned an incomplete response — please try again." + "storage_write_size_mismatch": "The downloaded file was smaller than expected. The upstream source may have returned an incomplete response — please try again.", + "storage_read_error": "An error occurred while reading the file from storage — please try again.", + "upstream_error": "A temporary error occurred while fetching from GitHub — please try again." }, "WARNINGS": { "page_not_enabled_on_repo": "GitHub Pages is not enabled on this repository. Enable it in the repository's Settings → Pages on GitHub, then refresh.", diff --git a/src/core/AnonymizedFile.ts b/src/core/AnonymizedFile.ts index 5ac16f8..12cd720 100644 --- a/src/core/AnonymizedFile.ts +++ b/src/core/AnonymizedFile.ts @@ -269,6 +269,7 @@ export default class AnonymizedFile { if (!config.STREAMER_ENTRYPOINT) { // collect the content locally const content = await this.content(); + content.on("error", (err) => anonymizer.destroy(err)); return content.pipe(anonymizer); } @@ -412,6 +413,7 @@ export default class AnonymizedFile { content .on("error", handleStreamError) .pipe(anonymizer) + .on("error", handleStreamError) .pipe(res) .on("error", handleStreamError) .on("finish", () => { diff --git a/src/core/storage/FileSystem.ts b/src/core/storage/FileSystem.ts index eb713fb..40133c2 100644 --- a/src/core/storage/FileSystem.ts +++ b/src/core/storage/FileSystem.ts @@ -228,10 +228,12 @@ export default class FileSystem extends StorageBase { await this.listFiles(repoId, dir, { onEntry: async (file) => { - let rs = await this.read(repoId, file.path); + let rs: Readable = await this.read(repoId, file.path); if (opt?.fileTransformer) { - // apply transformation on the stream - rs = rs.pipe(opt.fileTransformer(file.path)); + const src = rs; + const transformer = opt.fileTransformer(file.path); + src.on("error", (err) => transformer.destroy(err)); + rs = src.pipe(transformer); } const f = file.path.replace(fullPath, ""); archive.append(rs, { diff --git a/src/core/storage/S3.ts b/src/core/storage/S3.ts index 91d363b..2c20ffd 100644 --- a/src/core/storage/S3.ts +++ b/src/core/storage/S3.ts @@ -124,7 +124,13 @@ export default class S3Storage extends StorageBase { res.set("Content-Length", s.ContentLength.toString()); } if (s.Body) { - (s.Body as Readable)?.pipe(res); + const body = s.Body as Readable; + body.on("error", (err) => { + logger.error("S3 body stream error", { ...serializeError(err), filePath: path }); + if (!res.headersSent) res.status(502).json({ error: "storage_read_error" }); + else res.destroy(); + }); + body.pipe(res); } else { res.end(); } @@ -344,10 +350,12 @@ export default class S3Storage extends StorageBase { f.Key.replace(join(this.repoPath(repoId), dir), "") ); - let rs = await this.read(repoId, f.Key); + let rs: Readable = await this.read(repoId, f.Key); if (opt?.fileTransformer) { - // apply transformation on the stream - rs = rs.pipe(opt.fileTransformer(f.Key)); + const src = rs; + const transformer = opt.fileTransformer(f.Key); + src.on("error", (err) => transformer.destroy(err)); + rs = src.pipe(transformer); } archive.append(rs, {