{"record":{"id":"52a0ec16d14f2287","repo":"immich-app/immich","slug":"plugin-method-call-failed-label-methodname","errorCode":null,"errorMessage":"Plugin method call failed: ${label}#${methodName}","messagePattern":"Plugin method call failed: (.+?)#(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/repositories/plugin.repository.ts","lineNumber":261,"sourceCode":"\n  async callMethod<T>({ pluginKey, methodName }: PluginMethod, input: unknown, context?: unknown) {\n    const item = this.pluginMap.get(pluginKey);\n    if (!item) {\n      throw new Error(`No loaded plugin found for ${pluginKey}`);\n    }\n\n    const { pool, label } = item;\n\n    try {\n      const plugin = await pool.acquire();\n      try {\n        const result = await plugin.call(methodName, JSON.stringify(input), context);\n        return (result ? result.json() : result) as T;\n      } finally {\n        await pool.release(plugin);\n      }\n    } catch (error: Error | any) {\n      throw new Error(`Plugin method call failed: ${label}#${methodName}`, { cause: error });\n    }\n  }\n}\n","sourceCodeStart":243,"sourceCodeEnd":265,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/repositories/plugin.repository.ts#L243-L265","documentation":"callMethod acquires a worker from the plugin pool, calls the requested method, and wraps any failure (plugin throws, worker crashes, serialization error, timeout) in 'Plugin method call failed: <label>#<methodName>' with the cause preserved. The plugin itself remains loaded; only the method invocation failed.","triggerScenarios":"pool.acquire() or plugin.call(methodName, JSON.stringify(input), context) rejects — the plugin method threw, input was not JSON-serializable, the worker crashed mid-call, or the call timed out.","commonSituations":"Plugin method received unexpected input shape and threw internally; NaN/circular objects failing JSON.stringify; worker OOM or crash under load; bug in a newly deployed plugin version.","solutions":["Inspect the `cause` for the actual error thrown inside the plugin method","Validate/serialize the input payload before the call (ensure JSON.stringify works and shape matches the method contract)","Check worker logs/health for crashes (OOM, unhandled rejection) at the time of the call","Reproduce the method call with the same input directly against the plugin to debug"],"exampleFix":"// before\nconst r = await pluginRepo.callMethod({ pluginKey: 'geo-plugin', methodName: 'geocode' }, circularObj);\n// after\nconst r = await pluginRepo.callMethod({ pluginKey: 'geo-plugin', methodName: 'geocode' }, JSON.parse(JSON.stringify(sanitizedInput)));","handlingStrategy":"try-catch","validationCode":"// guard input serializability and shape before calling\nconst payload = JSON.parse(JSON.stringify(input)); // throws on circular refs early\nassertSchema(methodName, payload); // validate against the plugin's method contract","typeGuard":null,"tryCatchPattern":"try {\n  return await pluginRepo.callMethod({ pluginKey, methodName }, input, context);\n} catch (e) {\n  if (String(e.message).startsWith('Plugin method call failed')) {\n    logger.error(`${(e as any).cause?.message ?? e}`); // real error from the plugin\n    throw new PluginInvocationError(pluginKey, methodName, e);\n  }\n  throw e;\n}","preventionTips":["Version and validate the plugin method input contract (schema validation on both sides)","Log the cause — the wrapper message only names label#method","Keep plain JSON-serializable payloads (no circular refs, Dates, or class instances)","Watch worker metrics (crashes, OOM) and pin plugin versions during rollouts"],"tags":["plugin","worker","method-call"],"backgroundTag":"api-request-failed","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}