{"record":{"id":"3fa5323d158f4026","repo":"agalwood/Motrix","slug":"pluginruntimefault","errorCode":"PluginRuntimeFault","errorMessage":"plugin.command.access_denied","messagePattern":"plugin\\.command\\.access_denied","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/plugin/commands/cross-plugin-invoker.ts","lineNumber":132,"sourceCode":"    const taskId = this.taskIdProvider() ?? `_no_task_${callerId}_${startTs}`\n    const argsSize = measureBytes(args)\n\n    // Parse before entering the depth counter so a malformed commandId is\n    // a quick reject without touching shared state.\n    const parsed = parseCalleeId(commandId)\n    if (!parsed) {\n      const entry: CommandInvokeEntry = {\n        caller: callerId,\n        callee: '',\n        commandId,\n        argsSize,\n        durMs: Date.now() - startTs,\n        depth: this.depth.current(taskId),\n        ok: false,\n        errorCode: 'plugin.command.access_denied',\n      }\n      this.audit.log(entry)\n      throw new AppError(\n        ErrorCode.PluginRuntimeFault,\n        'plugin.command.access_denied'\n      )\n    }\n    const calleePluginId = parsed.pluginId\n\n    const depthValue = this.depth.enter(taskId)\n    const auditFail = (errorCode: string): never => {\n      this.audit.log({\n        caller: callerId,\n        callee: calleePluginId,\n        commandId,\n        argsSize,\n        durMs: Date.now() - startTs,\n        depth: depthValue,\n        ok: false,\n        errorCode,\n      })","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/commands/cross-plugin-invoker.ts#L114-L150","documentation":"Thrown at the very top of FullCrossPluginInvoker.execute when parseCalleeId(commandId) returns undefined — i.e. the command id does not contain at least three dot-separated segments (plugin.namespace.command). This early reject happens before the depth counter is entered and before any shared state is touched, so it is the cheapest possible access-denial path. The audit entry is logged with an empty callee and then AppError(PluginRuntimeFault) is raised.","triggerScenarios":"Calling invoker.execute(callerId, commandId, args) with a commandId like 'foo', 'foo.bar', or any value whose split('.').length < 3. The expected shape is 'vendor.plugin.commandName'.","commonSituations":"A caller plugin hardcodes a command id with a typo (missing the command segment), passes a user-supplied string that wasn't validated, or a manifest references a command by a short alias instead of its fully-qualified id. Also occurs when dynamically building commandIds from template strings that yield undefined fields.","solutions":["Verify the commandId format before invoking: it must have at least 3 dot segments.","Reference the exact id declared in the callee's manifest contributes.commands[].id.","If the id is user/template-derived, validate it against the format guard below and reject early with a clear caller-side error.","Add the corrected commandId to the caller manifest's invokesCommands array (required even for well-formed ids — see the later access check)."],"exampleFix":"// before\nawait invoker.execute(callerId, 'download', args)\n\n// after\nconst commandId = 'acme.fetcher.download'\nif (!/^\\w+\\.\\w+\\.[\\w.]+$/.test(commandId)) throw new Error('bad commandId')\nawait invoker.execute(callerId, commandId, args)","handlingStrategy":"validation","validationCode":"function isFullyQualifiedCommand(id: string): boolean { return id.split('.').length >= 3 }","typeGuard":"function isFullyQualifiedCommand(id: string): id is string {\n  const parts = id.split('.')\n  return parts.length >= 3 && parts.every((p) => p.length > 0)\n}","tryCatchPattern":"try { await invoker.execute(callerId, commandId, args) }\ncatch (e) { if (e.message === 'plugin.command.access_denied') { /* validate commandId format/declares */ } else throw e }","preventionTips":["Build commandIds from validated manifest constants, not string concatenation.","Ensure the caller's manifest invokesCommands lists the fully-qualified id.","Reject user/template-derived ids that don't match the 3-segment shape."],"tags":["plugin","commands","access-control","validation"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}