From e1cf8e3a00d2f91d8f0a044b35a6ea38935f21c3 Mon Sep 17 00:00:00 2001 From: tdurieux Date: Wed, 6 May 2026 12:26:12 +0300 Subject: [PATCH] Improve error handling --- src/core/logger.ts | 13 ++++++------- src/core/source/GitHubStream.ts | 29 +++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/core/logger.ts b/src/core/logger.ts index 29f13b6..a14590e 100644 --- a/src/core/logger.ts +++ b/src/core/logger.ts @@ -59,13 +59,12 @@ function getRedis(): RedisClientType | null { }, }) as RedisClientType; redisClient.on("error", () => { - if (!redisDisabled) { - redisDisabled = true; - try { - redisClient?.disconnect(); - } catch { - /* ignore */ - } + if (redisDisabled) return; + redisDisabled = true; + const c = redisClient; + redisClient = null; + if (c?.isOpen) { + c.disconnect().catch(() => {}); } }); redisClient.connect().catch(() => { diff --git a/src/core/source/GitHubStream.ts b/src/core/source/GitHubStream.ts index e5cd29d..9aed880 100644 --- a/src/core/source/GitHubStream.ts +++ b/src/core/source/GitHubStream.ts @@ -183,14 +183,35 @@ export default class GitHubStream extends GitHubBase { const stream1 = content.pipe(new stream.PassThrough()); const stream2 = content.pipe(new stream.PassThrough()); + // Safety net: guarantee an `error` listener exists on both branches + // before any error can be emitted. storage.write attaches its listener + // only after an `await mk(...)`, and the route handler attaches its + // listener after awaiting this function — both leave a window where + // an upstream error would have no listener and escalate to + // uncaughtException, crashing the streamer. + const noop = () => {}; + stream1.on("error", noop); + stream2.on("error", noop); + content.on("error", (error) => { - error = new AnonymousError("file_not_found", { - httpStatus: (error as { status?: number; httpStatus?: number }).status || (error as { httpStatus?: number }).httpStatus, + const httpStatus = + (error as { response?: { statusCode?: number } })?.response + ?.statusCode ?? + (error as { status?: number })?.status ?? + (error as { httpStatus?: number })?.httpStatus; + const code = + httpStatus === 422 + ? "file_too_big" + : httpStatus === 403 + ? "file_not_accessible" + : "file_not_found"; + const wrapped = new AnonymousError(code, { + httpStatus, cause: error as Error, object: filePath, }); - stream1.emit("error", error); - stream2.emit("error", error); + stream1.destroy(wrapped); + stream2.destroy(wrapped); }); storage.write(repoId, filePath, stream1, this.type);