{"record":{"id":"4a5fd9d7d9399962","repo":"zylon-ai/private-gpt","slug":"handler-must-define-function-handle-input-context","errorCode":null,"errorMessage":"Handler must define function handle(input, context).","messagePattern":"Handler must define function handle\\(input, context\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ui/index.html","lineNumber":7119,"sourceCode":"          currentCollection: state.context.documents.defaultCollection || \"pgpt_collection\",\n          log: (message, data) => {\n            logs.push({ message, data });\n            addDebugEvent(\"custom_tool:log\", { message, data });\n          }\n        });\n        const content = normalizeToolResult(result);\n        addDebugEvent(\"custom_tool:execute_success\", { message: `Executed ${tool.name}`, data: { result, logs } });\n        return { type: \"tool_result\", tool_use_id: toolUse.id, content, is_error: false };\n      } catch (error) {\n        addDebugEvent(\"custom_tool:execute_error\", { message: error.message, error: { message: error.message, stack: error.stack } });\n        return { type: \"tool_result\", tool_use_id: toolUse.id, content: error.message, is_error: true };\n      }\n    }\n\n    function compileHandler(source) {\n      const factory = new Function(`${source}; return handle;`);\n      const handle = factory();\n      if (typeof handle !== \"function\") throw new Error(\"Handler must define function handle(input, context).\");\n      return handle;\n    }\n\n    function normalizeToolResult(result) {\n      if (typeof result === \"string\") return result;\n      if (result && typeof result === \"object\" && typeof result.text === \"string\") return result.text;\n      return JSON.stringify(result ?? null);\n    }\n\n    async function testCustomTool(id) {\n      const tool = state.context.customTools.find(item => item.id === id);\n      if (!tool) return;\n      try {\n        const input = JSON.parse(tool.test_input_json || \"{}\");\n        const handle = compileHandler(tool.javascript_handler);\n        const result = await handle(input, {\n          fetch: window.fetch.bind(window),\n          localStorage: window.localStorage,","sourceCodeStart":7101,"sourceCodeEnd":7137,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/ui/index.html#L7101-L7137","documentation":"Raised by FileService.delete_file when the path is under uploads/ but storage.delete_file() returns a falsy result — i.e. no object existed at uploads-prefix + filename at delete time. This is a genuine not-found on deletion: either already deleted, uploaded under a different scope, or removed externally.","triggerScenarios":"Deleting the same file twice (double-click, retried request, or duplicate row in a UI); deleting a file uploaded under a different scope_id so the uploads prefix doesn't match; the object already removed by an external process.","commonSituations":"Non-idempotent delete in the client that retries on timeout after the first delete succeeded; race between two sessions deleting the same file; concurrent cleanup job.","solutions":["Treat 404 on delete as success (idempotent delete) in the client: the file is already gone.","Guard against double submits in the UI (disable the delete button while in flight).","Confirm the scope matches the upload scope if the file is expected to exist."],"exampleFix":"// before\nawait api.deleteFile(id); // second call raises 404\n\n// after\ntry {\n  await api.deleteFile(id);\n} catch (e) {\n  if (e.status !== 404) throw e; // 404 == already deleted, treat as ok\n}","handlingStrategy":"try-catch","validationCode":"// Check existence first if you need to distinguish\nconst info = await api.statFile(scopeId, fileId);\nif (!info) { /* already gone — nothing to do */ }","typeGuard":null,"tryCatchPattern":"try { await api.deleteFile(scopeId, fileId); }\ncatch (e) {\n  if (e.status === 404) return { deleted: true, alreadyGone: true }; // idempotent\n  throw e;\n}","preventionTips":["Make delete idempotent in the client: 404 means success","Disable the delete button while a delete is in flight","Scope file ids to the session/scope that created them"],"tags":["fastapi","http-404","file-storage","idempotency","race-condition"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}