agalwood/Motrix · error · CommandsError
plugin.commands.not_found
plugin.commands.not_found
Error message
command "${commandId}" is not registered What it means
Thrown by CommandsCapabilityHost.execute() when a plugin calls a command whose ID starts with its own namespace (e.g. `<callerId>.foo`) but no handler was ever registered for that exact commandId. The capability dispatches own-namespace calls directly from an in-memory handler map; a miss is a programming error, not a runtime race. The error code is `plugin.commands.not_found`.
Source
Thrown at src/core/plugin/capabilities/commands.ts:162
/**
* Execute a command.
*
* - Own-namespace (`commandId` starts with `${callerId}.`): dispatches to
* the registered handler. Rejects with `not_found` if absent. Any error
* thrown by the handler surfaces as-is.
* - Foreign-namespace: forwarded to the bound CrossPluginInvoker. Rejects
* with `access_denied` if no invoker is bound.
*/
async execute(
callerId: string,
commandId: string,
args: unknown
): Promise<unknown> {
if (commandId.startsWith(`${callerId}.`)) {
const handler = this.handlers.get(commandId)
if (!handler) {
throw new CommandsError(
'plugin.commands.not_found',
`command "${commandId}" is not registered`
)
}
const startTs = Date.now()
try {
const result = await handler(args)
this.onSelfInvoke?.({
callerId,
commandId,
durMs: Date.now() - startTs,
ok: true,
})
return result
} catch (err) {
this.onSelfInvoke?.({
callerId,
commandId,View on GitHub (pinned to 1a708ee577)
Solutions
- Verify the commandId string passed to execute() byte-for-byte matches the one passed to register(); check for trailing whitespace, casing, or a missing/extra segment.
- Ensure register() is called during plugin activation and that its returned CommandsRegistration has not been disposed before execute() runs.
- If the command is meant to be invoked cross-plugin (foreign namespace), drop the callerId prefix so it routes through the CrossPluginInvoker path instead.
- Search the plugin source for every register() call and confirm the target id is among them before invoking.
Example fix
// before
await cmds.execute('myPlugin', 'myPlugin.runTask', {})
// register() was never called for 'myPlugin.runTask'
// after
const reg = cmds.register('myPlugin.runTask', async (args) => doWork(args))
await cmds.execute('myPlugin', 'myPlugin.runTask', {}) Defensive patterns
Strategy: validation
Validate before calling
function isRegistered(cmds: CommandsCapabilityHost, id: string): boolean {
// expose a list/has on the host if available; otherwise track locally
return registeredIds.has(id)
}
if (!isRegistered(cmds, 'myPlugin.runTask')) {
throw new Error('command not registered; cannot execute')
} Type guard
function isCommandsError(e: unknown, code = 'plugin.commands.not_found'): e is CommandsError {
return e instanceof Error && (e as CommandsError).code === code
} Try / catch
try {
await cmds.execute(callerId, commandId, args)
} catch (e) {
if (isCommandsError(e, 'plugin.commands.not_found')) {
// register lazily or surface a clear 'command unavailable' message
} else throw e
} Prevention
- Track every register() id in a local Set and assert execute() targets are members.
- Register all commands in activate() and dispose them only in deactivate().
- Add a unit test that calls execute() for each id you registered.
When it happens
Trigger: Calling `execute(callerId, 'myPlugin.doThing', args)` where `callerId === 'myPlugin'` but `register('myPlugin.doThing', handler)` was never called (or was disposed). Also triggered by a typo in the commandId that still matches the namespace prefix, or by attempting to invoke a command after teardown/unregister.
Common situations: Plugin author forgot to register the command in their activate() lifecycle; manifest declares `contributes.commands[]` with id X but the handler registers under a slightly different id; a dispose() ran during teardown and a queued callback still calls execute().
Related errors
- plugin.commands.id_out_of_namespace
- plugin.command.not_declared_in_manifest
- plugin.commands.access_denied
- PluginRuntimeFault
- PluginRuntimeFault
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/5df19d9f09a27ea1.
Report an issue: GitHub.