From 31261190f00208f4126f4db38f1e1626c0061a12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:35:20 +0000 Subject: [PATCH 1/5] Initial plan From 82514e65e125ac2166f481ac1707c0d22a978109 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:43:11 +0000 Subject: [PATCH 2/5] Add server timeout configuration and improve error handling for script save operations - Configure HTTP server timeouts (requestTimeout: 5min, headersTimeout: 2min, keepAliveTimeout: 65s) - Add better error logging in PUT /scripts endpoint - Improve writeFileWithLock error handling with descriptive messages and proper cleanup - Ensure lock is always released even on error Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/api/script.ts | 2 ++ back/services/http.ts | 11 +++++++++ back/shared/utils.ts | 57 +++++++++++++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/back/api/script.ts b/back/api/script.ts index 1ecce902..d47ca99f 100644 --- a/back/api/script.ts +++ b/back/api/script.ts @@ -206,6 +206,7 @@ export default (app: Router) => { }), }), async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); try { let { filename, content, path } = req.body as { filename: string; @@ -223,6 +224,7 @@ export default (app: Router) => { await writeFileWithLock(filePath, content); return res.send({ code: 200 }); } catch (e) { + logger.error('🔥 error saving script: %o', e); return next(e); } }, diff --git a/back/services/http.ts b/back/services/http.ts index 5391c842..0bd8fcd7 100644 --- a/back/services/http.ts +++ b/back/services/http.ts @@ -16,6 +16,17 @@ export class HttpServerService { metricsService.record('http_service_start', 1, { port: port.toString(), }); + + // Set server timeouts to prevent premature connection drops + if (this.server) { + // Timeout for receiving the entire request (including body) - 5 minutes + this.server.requestTimeout = 300000; + // Timeout for headers - 2 minutes + this.server.headersTimeout = 120000; + // Keep-alive timeout - 65 seconds (slightly more than typical load balancer timeout) + this.server.keepAliveTimeout = 65000; + } + resolve(this.server); }); diff --git a/back/shared/utils.ts b/back/shared/utils.ts index fb344111..967f3d8d 100644 --- a/back/shared/utils.ts +++ b/back/shared/utils.ts @@ -3,6 +3,7 @@ import os from 'os'; import path from 'path'; import { writeFile, open, chmod } from 'fs/promises'; import { fileExist } from '../config/util'; +import Logger from '../loaders/logger'; function getUniqueLockPath(filePath: string) { const sanitizedPath = filePath @@ -19,24 +20,48 @@ export async function writeFileWithLock( if (typeof options === 'string') { options = { encoding: options }; } - if (!(await fileExist(filePath))) { - const fileHandle = await open(filePath, 'w'); - fileHandle.close(); + + try { + if (!(await fileExist(filePath))) { + const fileHandle = await open(filePath, 'w'); + await fileHandle.close(); + } + } catch (error) { + throw new Error(`Failed to create file ${filePath}: ${error}`); } + const lockfilePath = getUniqueLockPath(filePath); + let release: (() => Promise) | null = null; - const release = await lock(filePath, { - retries: { - retries: 10, - factor: 2, - minTimeout: 100, - maxTimeout: 3000, - }, - lockfilePath, - }); - await writeFile(filePath, content, { encoding: 'utf8', ...options }); - if (options?.mode) { - await chmod(filePath, options.mode); + try { + release = await lock(filePath, { + retries: { + retries: 10, + factor: 2, + minTimeout: 100, + maxTimeout: 3000, + }, + lockfilePath, + }); + } catch (error) { + throw new Error(`Failed to acquire lock for ${filePath}: ${error}`); + } + + try { + await writeFile(filePath, content, { encoding: 'utf8', ...options }); + if (options?.mode) { + await chmod(filePath, options.mode); + } + } catch (error) { + throw new Error(`Failed to write to file ${filePath}: ${error}`); + } finally { + if (release) { + try { + await release(); + } catch (error) { + // Log but don't throw on release failure + Logger.error(`Failed to release lock for ${filePath}:`, error); + } + } } - await release(); } From e1ce0f3fa996be78199b9c00077d3a4c465292d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:45:34 +0000 Subject: [PATCH 3/5] Improve error handling based on code review feedback - Use finally block to ensure file handle is closed even on error - Extract error messages properly using instanceof Error check - Improve error message formatting for better debugging Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/shared/utils.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/back/shared/utils.ts b/back/shared/utils.ts index 967f3d8d..8d650037 100644 --- a/back/shared/utils.ts +++ b/back/shared/utils.ts @@ -21,13 +21,19 @@ export async function writeFileWithLock( options = { encoding: options }; } - try { - if (!(await fileExist(filePath))) { - const fileHandle = await open(filePath, 'w'); - await fileHandle.close(); + // Ensure file exists before locking + if (!(await fileExist(filePath))) { + let fileHandle; + try { + fileHandle = await open(filePath, 'w'); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to create file ${filePath}: ${errorMessage}`); + } finally { + if (fileHandle) { + await fileHandle.close(); + } } - } catch (error) { - throw new Error(`Failed to create file ${filePath}: ${error}`); } const lockfilePath = getUniqueLockPath(filePath); @@ -44,7 +50,8 @@ export async function writeFileWithLock( lockfilePath, }); } catch (error) { - throw new Error(`Failed to acquire lock for ${filePath}: ${error}`); + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to acquire lock for ${filePath}: ${errorMessage}`); } try { @@ -53,7 +60,8 @@ export async function writeFileWithLock( await chmod(filePath, options.mode); } } catch (error) { - throw new Error(`Failed to write to file ${filePath}: ${error}`); + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to write to file ${filePath}: ${errorMessage}`); } finally { if (release) { try { From d30eb2008c68cf67447986de50830262c3718e87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:46:51 +0000 Subject: [PATCH 4/5] Add error handling for file handle close operation - Wrap fileHandle.close() in try-catch to prevent masking original errors - Log close errors without throwing to preserve error context Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/shared/utils.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/back/shared/utils.ts b/back/shared/utils.ts index 8d650037..8e51115b 100644 --- a/back/shared/utils.ts +++ b/back/shared/utils.ts @@ -31,7 +31,12 @@ export async function writeFileWithLock( throw new Error(`Failed to create file ${filePath}: ${errorMessage}`); } finally { if (fileHandle) { - await fileHandle.close(); + try { + await fileHandle.close(); + } catch (closeError) { + // Log close error but don't throw to avoid masking the original error + Logger.error(`Failed to close file handle for ${filePath}:`, closeError); + } } } } From 855f591992720c49c77a27c28253d3ce896a6913 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:49:27 +0000 Subject: [PATCH 5/5] Improve type safety for fileHandle and release variables - Add explicit FileHandle type annotation for fileHandle variable - Change fileHandle check from truthy to explicit undefined check - Change release type from null to undefined for better type safety Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/shared/utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/back/shared/utils.ts b/back/shared/utils.ts index 8e51115b..5f12eb23 100644 --- a/back/shared/utils.ts +++ b/back/shared/utils.ts @@ -1,7 +1,7 @@ import { lock } from 'proper-lockfile'; import os from 'os'; import path from 'path'; -import { writeFile, open, chmod } from 'fs/promises'; +import { writeFile, open, chmod, FileHandle } from 'fs/promises'; import { fileExist } from '../config/util'; import Logger from '../loaders/logger'; @@ -23,14 +23,14 @@ export async function writeFileWithLock( // Ensure file exists before locking if (!(await fileExist(filePath))) { - let fileHandle; + let fileHandle: FileHandle | undefined; try { fileHandle = await open(filePath, 'w'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to create file ${filePath}: ${errorMessage}`); } finally { - if (fileHandle) { + if (fileHandle !== undefined) { try { await fileHandle.close(); } catch (closeError) { @@ -42,7 +42,7 @@ export async function writeFileWithLock( } const lockfilePath = getUniqueLockPath(filePath); - let release: (() => Promise) | null = null; + let release: (() => Promise) | undefined; try { release = await lock(filePath, {