zylon-ai/private-gpt · error · Error
Handler must define function handle(input, context).
Error message
Handler must define function handle(input, context).
What it means
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.
Source
Thrown at ui/index.html:7119
currentCollection: state.context.documents.defaultCollection || "pgpt_collection",
log: (message, data) => {
logs.push({ message, data });
addDebugEvent("custom_tool:log", { message, data });
}
});
const content = normalizeToolResult(result);
addDebugEvent("custom_tool:execute_success", { message: `Executed ${tool.name}`, data: { result, logs } });
return { type: "tool_result", tool_use_id: toolUse.id, content, is_error: false };
} catch (error) {
addDebugEvent("custom_tool:execute_error", { message: error.message, error: { message: error.message, stack: error.stack } });
return { type: "tool_result", tool_use_id: toolUse.id, content: error.message, is_error: true };
}
}
function compileHandler(source) {
const factory = new Function(`${source}; return handle;`);
const handle = factory();
if (typeof handle !== "function") throw new Error("Handler must define function handle(input, context).");
return handle;
}
function normalizeToolResult(result) {
if (typeof result === "string") return result;
if (result && typeof result === "object" && typeof result.text === "string") return result.text;
return JSON.stringify(result ?? null);
}
async function testCustomTool(id) {
const tool = state.context.customTools.find(item => item.id === id);
if (!tool) return;
try {
const input = JSON.parse(tool.test_input_json || "{}");
const handle = compileHandler(tool.javascript_handler);
const result = await handle(input, {
fetch: window.fetch.bind(window),
localStorage: window.localStorage,View on GitHub (pinned to 4a030776a3)
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.
Example fix
// before
await api.deleteFile(id); // second call raises 404
// after
try {
await api.deleteFile(id);
} catch (e) {
if (e.status !== 404) throw e; // 404 == already deleted, treat as ok
} Defensive patterns
Strategy: try-catch
Validate before calling
// Check existence first if you need to distinguish
const info = await api.statFile(scopeId, fileId);
if (!info) { /* already gone — nothing to do */ } Try / catch
try { await api.deleteFile(scopeId, fileId); }
catch (e) {
if (e.status === 404) return { deleted: true, alreadyGone: true }; // idempotent
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid message order: expected {expected_roles} after {prev
- Audio size {audio_size} exceeds maximum allowed size of {set
- Unsupported content block type: {type(block)}
- Unknown message role {self.role}. Expected 'system', 'user',
- Invalid tool specification: {tool}
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/4a5fd9d7d9399962.
Report an issue: GitHub.