{"record":{"id":"5c090c0135535fa5","repo":"agalwood/Motrix","slug":"plugin-lifecycle-activation-capability-violation","errorCode":"plugin.lifecycle.activation_capability_violation","errorMessage":"effectful call ${capability}.${method} during activation","messagePattern":"effectful call (.+?)\\.(.+?) during activation","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/core/plugin/host/quick-js-worker.ts","lineNumber":94,"sourceCode":"const registeredDeactivateHandlers: QuickJSHandle[] = []\n\nfunction send(msg: WorkerToHost): void {\n  port.postMessage(msg)\n}\n\nfunction assertEffectfulAllowed(capability: string, method: string): void {\n  if (currentPhase !== 'activation') return\n  if (classify(capability, method) !== 'effectful') return\n  violationFatal = {\n    code: 'plugin.lifecycle.activation_capability_violation',\n    message: `effectful call ${capability}.${method} during activation`,\n  }\n  send({\n    type: 'fatal',\n    code: violationFatal.code,\n    message: violationFatal.message,\n  })\n  throw new Error(violationFatal.message)\n}\n\nasync function callHost(\n  capability: string,\n  method: string,\n  args: unknown[]\n): Promise<unknown> {\n  const id = nextCallId++\n  return new Promise((resolve, reject) => {\n    pendingCalls.set(id, (resp) => {\n      if (resp.type !== 'response') return\n      if (resp.ok) {\n        resolve(resp.result)\n      } else {\n        const e: Error & { code?: string } = new Error(resp.error.message)\n        e.code = resp.error.code\n        reject(e)\n      }","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/host/quick-js-worker.ts#L76-L112","documentation":"During module evaluation (currentPhase === 'activation') only registration-only calls are allowed: hooks.beforeCreate/beforeFinalize/afterComplete/onError, commands.register, and lifecycle.onActivate/onDeactivate. Any effectful call (http, fs.task, fs.storage, storage, notify, ffmpeg, crypto, config, metadata, commands.execute, i18n.t) is fatal — the worker sends a 'fatal' message and throws, killing the plugin. (log.* is an explicit exception and is permitted at top level.)","triggerScenarios":"A plugin calls notify.show, http.get, storage.set, ffmpeg.*, crypto.*, config.get, or i18n.t at top-level module scope rather than inside a registered hook/command handler or onActivate.","commonSituations":"Plugin runs setup work at import time; reads config during module init; opens a connection in module scope; calls i18n.t to build a top-level constant.","solutions":["Move effectful work into onActivate or a hook/command handler; keep module top-level to registration only.","If a value is needed at top-level, compute it lazily inside a hook instead.","Use log.* (the permitted exception) only for diagnostics, not for state-changing work."],"exampleFix":"// before — effectful call at module top level\nconst cfg = await config.get('key')\nhooks.beforeCreate(async (ctx) => { /* uses cfg */ })\n// after — defer into a hook\nhooks.beforeCreate(async (ctx) => {\n  const cfg = await config.get('key')\n  /* ... */\n})","handlingStrategy":"validation","validationCode":"import { classify } from '../capabilities/classification'\n// at plugin authoring/review time: assert top-level calls are registration-only\nfunction assertTopLevelAllowed(capability: string, method: string): void {\n  const c = classify(capability, method)\n  if (c !== 'registration-only') {\n    throw new Error(`${capability}.${method} is ${c}; move it out of module top level into onActivate or a hook`)\n  }\n}","typeGuard":"import { isRegistrationOnly } from '../capabilities/classification'\nfunction isAllowedAtTopLevel(capability: string, method: string): boolean {\n  return isRegistrationOnly(capability, method) || capability === 'log'\n}","tryCatchPattern":"// this error is fatal and kills the plugin; there is no in-process recovery.\n// wrap setup work so it never runs at module scope:\nhooks.beforeCreate(async () => {\n  try { /* effectful setup */ } catch (e) { /* handle inside the hook */ }\n})","preventionTips":["Keep module top-level to registration-only calls (hooks.*, commands.register, lifecycle.*).","Move any effectful setup into onActivate or a hook/command handler.","Remember log.* is the only effectful-classified exception permitted at top level."],"tags":["plugin","lifecycle","activation","fatal"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}