agalwood/Motrix · error · CommandsError
plugin.command.not_declared_in_manifest
plugin.command.not_declared_in_manifest
Error message
command "${commandId}" is not declared in plugin "${callerId}" manifest.contributes.commands[] What it means
CommandsError with code 'plugin.command.not_declared_in_manifest', thrown by CommandsCapabilityHost.register when commandId is in the caller's namespace but is not present in the set returned by the manifestCommandIds resolver for that caller. The capability requires that every registered command be statically declared in the plugin's manifest.contributes.commands[] — a manifest-driven contract enforced only when a manifestCommandIds resolver is wired (production); in tests without a resolver this check is skipped for back-compat.
Source
Thrown at src/core/plugin/capabilities/commands.ts:123
* @throws {CommandsError} plugin.commands.id_out_of_namespace — wrong owner
* @throws {CommandsError} plugin.command.not_declared_in_manifest — missing from manifest
*/
register(
callerId: string,
commandId: string,
handler: CommandHandler
): CommandsRegistration {
if (!commandId.startsWith(`${callerId}.`)) {
throw new CommandsError(
'plugin.commands.id_out_of_namespace',
`command "${commandId}" is outside namespace "${callerId}." — commands must start with "${callerId}."`
)
}
if (this.resolveDeclared) {
const declared = this.resolveDeclared(callerId)
if (!declared?.has(commandId)) {
throw new CommandsError(
'plugin.command.not_declared_in_manifest',
`command "${commandId}" is not declared in plugin "${callerId}" manifest.contributes.commands[]`
)
}
}
if (this.handlers.has(commandId)) {
console.warn(
`[plugin:commands] handler for "${commandId}" registered more than once; previous handler replaced`
)
}
this.handlers.set(commandId, handler)
return {
dispose: () => {
this.handlers.delete(commandId)
},View on GitHub (pinned to 1a708ee577)
Solutions
- Add the command ID to manifest.contributes.commands[] for the plugin.
- Confirm exact string match (case, dot, no trailing whitespace) between the manifest entry and the registered commandId.
- Reload/rebuild the manifest resolver so it serves the updated set.
- In tests where you intentionally skip declaration, construct CommandsCapabilityHost without manifestCommandIds.
Example fix
// before — manifest.contributes.commands = [{ id: 'myPlugin.other' }]
host.register('myPlugin', 'myPlugin.doThing', handler)
// after — declare it in the manifest
// manifest.contributes.commands = [
// { id: 'myPlugin.other' },
// { id: 'myPlugin.doThing' },
// ]
host.register('myPlugin', 'myPlugin.doThing', handler) Defensive patterns
Strategy: validation
Validate before calling
function isDeclaredInManifest(
declared: ReadonlySet<string> | undefined,
commandId: string
): boolean {
return !declared || declared.has(commandId)
}
const declared = manifestCommandIds?.(callerId)
if (!isDeclaredInManifest(declared, commandId)) {
throw new Error(`${commandId} missing from manifest.contributes.commands[]`)
}
host.register(callerId, commandId, handler) Type guard
function isDeclaredInManifest(declared: ReadonlySet<string> | undefined, commandId: string): boolean {
return !declared || declared.has(commandId)
} Try / catch
try {
host.register(callerId, commandId, handler)
} catch (e) {
if (e instanceof CommandsError && e.code === 'plugin.command.not_declared_in_manifest') {
// add commandId to manifest.contributes.commands[] and reload
} else throw e
} Prevention
- Keep a single source of truth for command IDs and generate both the manifest entry and the register() call from it.
- Add a CI step that diffs registered command IDs against manifest.contributes.commands[].
- In unit tests, construct CommandsCapabilityHost without manifestCommandIds to skip the declaration check deliberately.
When it happens
Trigger: Calling host.register('myPlugin', 'myPlugin.doThing', handler) where 'myPlugin.doThing' is not listed in the plugin's manifest.contributes.commands[] AND the host was constructed with a manifestCommandIds resolver (as capability-host.ts does in production). Distinguishable from error 78 because the namespace check passed.
Common situations: A new command was added in code but the manifest was not updated; the manifest entry has a typo or different casing; the plugin id in the manifest differs from the callerId passed to register; manifest reload did not pick up a freshly added command; resolver returns a stale snapshot.
Related errors
- plugin.commands.id_out_of_namespace
- plugin.commands.not_found
- plugin.commands.access_denied
- PluginRuntimeFault
- PluginManifestInvalid
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/b8a86cf05a712eda.
Report an issue: GitHub.