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

  1. Treat 404 on delete as success (idempotent delete) in the client: the file is already gone.
  2. Guard against double submits in the UI (disable the delete button while in flight).
  3. 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

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


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/4a5fd9d7d9399962. Report an issue: GitHub.