{"record":{"id":"38cae32309d1e315","repo":"mem0ai/mem0","slug":"cancelled","errorCode":null,"errorMessage":"Cancelled","messagePattern":"Cancelled","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"info","filePath":"integrations/pi-agent-plugin/src/memory/tools.ts","lineNumber":57,"sourceCode":"interface ToolParams {\n  action: \"search\" | \"add\" | \"get_all\" | \"update\" | \"delete\" | \"delete_all\";\n  query?: string;\n  content?: string;\n  memory_id?: string;\n  scope?: Scope;\n}\n\nexport function buildToolExecute(\n  mem0: MemoryClient,\n  scopeCtx: ScopeContext,\n  defaultScope: Scope,\n) {\n  return async (params: ToolParams, signal?: AbortSignal) => {\n    const scope = params.scope ?? defaultScope;\n\n    switch (params.action) {\n      case \"search\": {\n        if (signal?.aborted) throw new Error(\"Cancelled\");\n        if (!params.query) throw new Error(\"query is required for search\");\n        const filters = resolveSearchFilters(scope, scopeCtx);\n        const result = await mem0.search(params.query, { filters });\n        const memories = result.results ?? [];\n        return {\n          content: [{ type: \"text\" as const, text: truncateOutput(formatMemoryList(memories)) }],\n          details: { matchCount: memories.length },\n        };\n      }\n\n      case \"add\": {\n        if (signal?.aborted) throw new Error(\"Cancelled\");\n        if (!params.content) throw new Error(\"content is required for add\");\n        const addParams = resolveAddParams(scope, scopeCtx);\n        const result = await mem0.add(\n          [{ role: \"user\", content: params.content }],\n          { ...addParams, customCategories: DEFAULT_CUSTOM_CATEGORIES },\n        );","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/integrations/pi-agent-plugin/src/memory/tools.ts#L39-L75","documentation":"Cooperative-cancellation error thrown by the pi-agent-plugin memory tool's 'search' action when the caller-provided AbortSignal is already aborted before the (potentially expensive) Mem0 search request runs. The tool checks signal.aborted at the start of each action to stop work promptly when the user or agent cancels the turn.","triggerScenarios":"The agent runtime passes an AbortSignal that was aborted (user pressed Esc / cancelled the tool call / turn timeout) and the tool executor still invokes the search action with that signal.","commonSituations":"User cancels while a tool call is queued; agent framework aborts in-flight tools when a newer turn starts; signal propagated from a request that already timed out.","solutions":["Treat as control flow, not a bug: catch it and stop the calling workflow without retrying.","If cancellation is unexpected, audit where the AbortSignal originates (agent runtime / HTTP request) — something upstream aborted the turn.","Don't call the tool after awaiting a cancelled operation; check signal.aborted in your own loop first."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (signal?.aborted) return { content: [{ type: 'text', text: 'cancelled' }], details: {} };","typeGuard":null,"tryCatchPattern":"try {\n  return await toolExecute(params, signal);\n} catch (err) {\n  if ((err as Error).message === 'Cancelled') return { cancelled: true };\n  throw err;\n}","preventionTips":["Check signal.aborted in your dispatcher before invoking tools.","Treat 'Cancelled' as control flow — end the turn, never retry."],"tags":["cancellation","abort-signal","agent-tool","typescript"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}