agalwood/Motrix · error · PluginCodedError

plugin.capability.bad_args

plugin.capability.bad_args

Error message

log.${msg.method}: invalid args

What it means

Thrown by CapabilityBridge.dispatchLog when logArgsSchema.safeParse(msg.args) fails. logArgsSchema is z.tuple([z.string()]).rest(z.unknown()), so the first argument MUST be a string. Tagged plugin.capability.bad_args and marshaled back to the plugin VM as a coded error.

Source

Thrown at src/core/plugin/host/capability-bridge.ts:597

   * always permitted (auto-injected). When `effectivePermissions` is
   * undefined, all capabilities are permitted (back-compat for tests).
   */
  private permitted(capability: string): boolean {
    const required = CAPABILITY_PERMISSIONS[capability]
    if (!required) return true
    const eff = this.opts.effectivePermissions
    if (!eff) return true
    return required.some((p) => eff.has(p))
  }

  // -------------------------------------------------------------------------
  // log
  // -------------------------------------------------------------------------

  private dispatchLog(msg: BridgeCallMessage): void {
    const parsed = logArgsSchema.safeParse(msg.args)
    if (!parsed.success) {
      throw new PluginCodedError(
        'plugin.capability.bad_args',
        `log.${msg.method}: invalid args`
      )
    }
    const [text, ...rest] = parsed.data
    const fields = rest[0] as Record<string, unknown> | undefined
    const fn = (
      this.log as unknown as Record<string, (m: string, f?: object) => void>
    )[msg.method]
    if (typeof fn !== 'function') {
      throw new PluginCodedError(
        'plugin.capability.unavailable',
        `unknown log method: ${msg.method}`
      )
    }
    fn.call(this.log, String(text), fields)
  }

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Make the first log argument a string message: log.info('download started', { bytes }).
  2. Pass structured fields only as the optional second argument.
  3. If migrating from a logger that accepts object-first calls, add a shim in the worker proxy that swaps the args.

Example fix

// before
log.info({ bytes: 1024 })
// after
log.info('chunk received', { bytes: 1024 })
Defensive patterns

Strategy: validation

Validate before calling

import { z } from 'zod'
const logArgsSchema = z.tuple([z.string()]).rest(z.unknown())
// Validate before forwarding to the bridge.
if (!logArgsSchema.safeParse(args).success) {
  args = [String(args[0] ?? '')].concat(args.slice(1))
}

Type guard

function isLogCall(args: unknown[]): args is [string, ...unknown[]] {
  return typeof args[0] === 'string'
}

Try / catch

try { log.info(...args) }
catch (e) {
  if (e instanceof Error && e.code === 'plugin.capability.bad_args') {
    log.info(String(args[0] ?? ''), args[1]) // coerce first arg to string
    return
  }
  throw e
}

Prevention

When it happens

Trigger: A plugin calls log.info(/warn/error/debug/trace) with a non-string first argument — e.g. log.info({code:1}), log.info(42), log.info(undefined). The safeParse at capability-bridge.ts:595 fails and the throw at 597-600 fires.

Common situations: Plugin passes a fields object as the first arg instead of a message string; worker-side log proxy forwards args in the wrong order; plugin assumes log accepts an object-only call like pino/winston; arity mismatch after a bridge refactor.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/8ecbe8221124de24. Report an issue: GitHub.