{"record":{"id":"fe632eec08634c1a","repo":"SillyTavern/SillyTavern","slug":"internal-server-error-fe632e","errorCode":null,"errorMessage":"Internal Server Error","messagePattern":"Internal Server Error","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"src/endpoints/chats.js","lineNumber":675,"sourceCode":"            request.body.chatfile += '.jsonl';\n        }\n\n        const dirName = String(request.body.avatar_url).replace('.png', '');\n        const chatFileName = String(request.body.chatfile);\n        const chatFilePath = path.join(request.user.directories.chats, dirName, sanitize(chatFileName));\n        if (!isPathUnderParent(request.user.directories.chats, chatFilePath)) {\n            return response.sendStatus(400);\n        }\n        //Return success if the file was deleted.\n        if (tryDeleteFile(chatFilePath)) {\n            return response.send({ ok: true });\n        } else {\n            console.error('The chat file was not deleted.');\n            return response.sendStatus(400);\n        }\n    } catch (error) {\n        console.error(error);\n        return response.sendStatus(500);\n    }\n});\n\nrouter.post('/export', validateAvatarUrlMiddleware, async function (request, response) {\n    if (!request.body.file || (!request.body.avatar_url && request.body.is_group === false)) {\n        return response.sendStatus(400);\n    }\n    const pathToFolder = request.body.is_group\n        ? request.user.directories.groupChats\n        : path.join(request.user.directories.chats, String(request.body.avatar_url).replace('.png', ''));\n    const filename = path.join(pathToFolder, sanitize(request.body.file));\n    if (!request.body.is_group && !isPathUnderParent(request.user.directories.chats, filename)) {\n        return response.sendStatus(400);\n    }\n    let exportfilename = request.body.exportfilename;\n    if (!fs.existsSync(filename)) {\n        const errorMessage = {\n            message: `Could not find JSONL file to export. Source chat file: ${filename}.`,","sourceCodeStart":657,"sourceCodeEnd":693,"githubUrl":"https://github.com/SillyTavern/SillyTavern/blob/06bde939fb1e9c4c8d8641d810f0a916b5bce127/src/endpoints/chats.js#L657-L693","documentation":"Returned by POST /api/chats/delete as a bare 500 from the catch block when an unexpected exception is thrown inside the handler. tryDeleteFile itself only returns false (not throw), so the exception comes from elsewhere: String(request.body.chatfile) on a non-coercible body, path.join on invalid input, or fs.unlinkSync raising EBUSY/EACCES/EPERM despite the existsSync check (a TOCTOU race where the file is locked or removed between the check and unlink).","triggerScenarios":"On Windows, another process (editor, antivirus, indexer) holds the jsonl open so unlinkSync throws EBUSY; permissions changed between existsSync and unlinkSync; request.body.chatfile is null/non-string in a way that breaks path operations.","commonSituations":"Antivirus or cloud-sync (OneDrive) locking chat files on Windows; a network-mounted data volume with intermittent permission errors.","solutions":["Inspect the server console — console.error(error) prints the syscall code and path.","Close any editor/IDE/terminal that has the chat file open, and exclude the data folder from antivirus/on-demand scanning.","Retry the delete after a moment if a transient lock was the cause.","Verify filesystem permissions on the chats directory."],"exampleFix":"// before: existsSync then unlinkSync can race on Windows\nif (fs.existsSync(filePath)) { fs.unlinkSync(filePath); return true; }\n\n// after: tolerate a vanished-or-locked file\ntry { fs.unlinkSync(filePath); return true; }\ncatch (e) { if (e.code === 'ENOENT') return false; throw e; }","handlingStrategy":"retry","validationCode":"// Server-side: replace existsSync+unlinkSync with a race-tolerant delete in tryDeleteFile\n// (validation happens by checking error.code === 'ENOENT' in the catch)","typeGuard":null,"tryCatchPattern":"async function deleteChat(path) {\n  for (let attempt = 0; attempt < 3; attempt++) {\n    try { await fsPromises.unlink(path); return true; }\n    catch (e) {\n      if (e.code === 'ENOENT') return false;     // already gone\n      if (e.code === 'EBUSY' || e.code === 'EPERM') { await sleep(200); continue; }\n      throw e;\n    }\n  }\n  throw new Error('file locked');\n}","preventionTips":["Exclude the data folder from antivirus/cloud-sync real-time scanning on Windows.","Close editors holding chat files before deleting.","Retry once on EBUSY/EPERM before surfacing the 500."],"tags":["filesystem","http-500","chat-delete","toctou"],"backgroundTag":null,"analyzedSha":"06bde939fb1e9c4c8d8641d810f0a916b5bce127","analyzedAt":"2026-08-13T07:48:40.832Z","contentChangedAt":"2026-08-13T07:48:40.832Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}