agalwood/Motrix · error · PluginCodedError
plugin.metadata.not_available_outside_hook
plugin.metadata.not_available_outside_hook
Error message
metadata is only available inside a hook invocation; no task context is active
What it means
Thrown by dispatchMetadata when this.currentTaskId is falsy. metadata is scoped to the current task and is only available while a hook invocation is active (the host sets currentTaskId via BridgeHookEnter). Calling metadata.* outside a hook throws plugin.metadata.not_available_outside_hook.
Source
Thrown at src/core/plugin/host/capability-bridge.ts:849
? (args.data[0] as string | undefined)
: undefined
return host.keys(pluginId, prefix)
}
default:
throw new PluginCodedError(
'plugin.capability.unavailable',
`unknown storage method: ${msg.method}`
)
}
}
// -------------------------------------------------------------------------
// metadata — requires hook context (taskId). Task 19 throws when none.
// -------------------------------------------------------------------------
private async dispatchMetadata(msg: BridgeCallMessage): Promise<unknown> {
if (!this.currentTaskId) {
throw new PluginCodedError(
'plugin.metadata.not_available_outside_hook',
'metadata is only available inside a hook invocation; no task context is active'
)
}
const host = this.opts.capabilityHost.metadata
const pluginId = this.opts.pluginId
const taskId = this.currentTaskId
switch (msg.method) {
case 'get': {
const [key] = metadataGetSchema.parse(msg.args)
return host.get(taskId, pluginId, key)
}
case 'has': {
const [key] = metadataGetSchema.parse(msg.args)
return host.has(taskId, pluginId, key)
}
case 'getAll':
return host.getAll(taskId, pluginId)View on GitHub (pinned to 1a708ee577)
Solutions
- Call metadata.* only inside beforeCreate/beforeFinalize hook handlers where the task context is active.
- For cross-task plugin state (not bound to one task), use the storage KV capability instead.
- In tests, drive the bridge through setHookContext({taskId, fsTaskHost}) before invoking metadata.
Example fix
// before — onActivate
async function onActivate() { await metadata.get('config') }
// after — inside a hook
export function beforeCreate(ctx) { const cfg = await ctx.metadata.get('config') } Defensive patterns
Strategy: type-guard
Validate before calling
// Track hook entry/exit in the plugin.
let inHook = false
export function beforeCreate(ctx) { inHook = true; try { /* ... */ } finally { inHook = false } }
function assertTaskContext() { if (!inHook) throw new Error('metadata requires a hook context') } Type guard
function hasTaskContext(ctx: unknown): ctx is { metadata: object } {
return !!ctx && typeof (ctx as any)?.metadata === 'object'
} Try / catch
try { return await metadata.get(key) }
catch (e) {
if (e instanceof Error && e.code === 'plugin.metadata.not_available_outside_hook') {
// read from persistent storage instead, or defer to next hook
return undefined
}
throw e
} Prevention
- Use storage (KV) for cross-task plugin state; reserve metadata for task-bound data inside hooks.
- Do not capture metadata at activation time for later use.
- In tests, setHookContext({taskId, fsTaskHost}) before exercising metadata.
When it happens
Trigger: Plugin calls metadata.get/set/has/getAll/keys/delete from module top-level, onActivate, onStartup, or after the hook has returned. The guard at capability-bridge.ts:848-853 fires before the method switch.
Common situations: Plugin caches a metadata reference at activation and calls it lazily later; plugin reads metadata during initialize(); hook context not wired in a test harness.
Related errors
- plugin.fs.task.not_available_outside_hook
- plugin.lifecycle.secrets_seed_missing
- plugin.lifecycle.deactivate_timeout
- PluginActivationCapExceeded
- plugin.capability.bad_args
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/abeeb0bed28ad436.
Report an issue: GitHub.