agalwood/Motrix · error · MetadataError

plugin.metadata.quota_exceeded

plugin.metadata.quota_exceeded

Error message

plugin.metadata.quota_exceeded: projected usage ${projected} exceeds quota ${this.perPluginPerTaskBytes}

What it means

Before writing, projected total size for this (task, plugin) is computed as currentTotal - existingSizeOfKey + newSizeOfKey and compared against perPluginPerTaskBytes (default 64 KB). The error fires on the would-be new total, so an overwrite of a large key with a small one can succeed even when near quota. The quota is per-plugin-per-task, not global.

Source

Thrown at src/core/plugin/capabilities/metadata.ts:136

      .prepare<[string, string], { total: number }>(
        `SELECT COALESCE(SUM(size), 0) AS total
         FROM plugin_task_metadata
         WHERE task_id = ? AND plugin_id = ?`
      )
      .get(taskId, pluginId) ?? { total: 0 }

    const curRow = this.db
      .prepare<[string, string, string], { size: number } | undefined>(
        `SELECT size FROM plugin_task_metadata
         WHERE task_id = ? AND plugin_id = ? AND key = ?`
      )
      .get(taskId, pluginId, key)

    const currentSize = curRow?.size ?? 0
    const projected = totRow.total - currentSize + projectedSize

    if (projected > this.perPluginPerTaskBytes) {
      throw new MetadataError(
        'plugin.metadata.quota_exceeded',
        `plugin.metadata.quota_exceeded: projected usage ${projected} exceeds quota ${this.perPluginPerTaskBytes}`
      )
    }
  }

  // -------------------------------------------------------------------------
  // get
  // -------------------------------------------------------------------------

  async get(taskId: string, pluginId: string, key: string): Promise<unknown> {
    const row = this.db
      .prepare<[string, string, string], { value: string } | undefined>(
        `SELECT value FROM plugin_task_metadata
         WHERE task_id = ? AND plugin_id = ? AND key = ?`
      )
      .get(taskId, pluginId, key)

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Delete or shrink keys you no longer need before writing new ones.
  2. Move large blobs (>64 KB) to the storage capability (default quota 5 MB) or fs.
  3. Raise perPluginPerTaskBytes in MetadataCapabilityHost options if the runtime policy allows.
  4. Compress or project values to the minimal JSON shape before set().

Example fix

// before
await metadata.set(taskId, pluginId, 'log', JSON.stringify(hugeLog))

// after
await metadata.delete(taskId, pluginId, 'log')
await metadata.set(taskId, pluginId, 'summary', { count, lastError })
Defensive patterns

Strategy: validation

Validate before calling

const projected = (currentTotal - currentKeySize) + Buffer.byteLength(json, 'utf8')
if (projected > perPluginPerTaskBytes) {
  // delete unused keys first, or route the blob to storage/fs
}

Try / catch

try {
  await metadata.set(taskId, pluginId, key, value)
} catch (e) {
  if (e instanceof MetadataError && e.code === 'plugin.metadata.quota_exceeded') {
    await pruneOldKeys(taskId, pluginId)
    await metadata.set(taskId, pluginId, key, value)
  } else throw e
}

Prevention

When it happens

Trigger: Accumulating many keys under one task/plugin; storing a single large JSON value (>64 KB) under one key; repeatedly writing larger and larger values without removing old ones.

Common situations: Plugin caches per-task results; plugin stores request/response logs; long-lived task accumulates state; default 64 KB quota smaller than plugin assumed.

Related errors


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