agalwood/Motrix · error · StorageError

plugin.storage.quota_exceeded

plugin.storage.quota_exceeded

Error message

plugin.storage.quota_exceeded: projected usage ${projected} exceeds quota ${this.quotaBytes}

What it means

Thrown by assertQuota() before any write when the projected total bytes for that plugin would exceed quotaBytes (default 5 MB, override via constructor). The projection subtracts the existing size of the same key (so re-writing the same key with a smaller/equal value does not double-count) and adds the incoming value's UTF-8 byte length. It is a pre-flight guard, so the row is never partially written.

Source

Thrown at src/core/plugin/capabilities/storage.ts:144

    projectedSize: number
  ): void {
    const row = this.db
      .prepare<[string], { total: number }>(
        'SELECT COALESCE(SUM(size), 0) AS total FROM plugin_storage WHERE plugin_id = ?'
      )
      .get(pluginId) ?? { total: 0 }

    const currentRow = this.db
      .prepare<[string, string], { size: number } | undefined>(
        'SELECT size FROM plugin_storage WHERE plugin_id = ? AND key = ?'
      )
      .get(pluginId, key)

    const currentSize = currentRow?.size ?? 0
    const projected = row.total - currentSize + projectedSize

    if (projected > this.quotaBytes) {
      throw new StorageError(
        'plugin.storage.quota_exceeded',
        `plugin.storage.quota_exceeded: projected usage ${projected} exceeds quota ${this.quotaBytes}`
      )
    }
  }

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

  async get(pluginId: string, key: string): Promise<StorageGetResult> {
    const row = this.db
      .prepare<
        [string, string],
        { value: string; version: number } | undefined
      >(
        'SELECT value, version FROM plugin_storage WHERE plugin_id = ? AND key = ?'
      )

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Reduce the value size — compress, downsample, or trim the structure before storing.
  2. Delete or overwrite old keys (host.delete / host.keys + cleanup loop) to bring the running total down before writing the new key.
  3. If the plugin legitimately needs more space, construct StorageCapabilityHost with a larger quotaBytes: new StorageCapabilityHost({ db, quotaBytes: 20 << 20 }).
  4. Split one large value across multiple plugins only if the data model genuinely shards; otherwise prefer trimming.

Example fix

// before
const host = new StorageCapabilityHost({ db })
await host.set(pluginId, 'log', bigLogString)

// after
const host = new StorageCapabilityHost({ db, quotaBytes: 20 << 20 })
await host.set(pluginId, 'log', bigLogString.slice(0, 2_000_000))
Defensive patterns

Strategy: validation

Validate before calling

const totalBytes = (await host.keys(pluginId)).reduce(async (acc, k) => acc + (await host.get(pluginId, k))?.__size ?? 0, 0)
// simpler: pre-check incoming size
if (Buffer.byteLength(JSON.stringify(value) ?? '', 'utf8') + approxCurrentTotal > QUOTA) { /* trim first */ }

Try / catch

try { await host.set(pluginId, key, value) }
catch (e) { if (e.code === 'plugin.storage.quota_exceeded') { await cleanupOldKeys(pluginId); await host.set(pluginId, key, value) } else throw e }

Prevention

When it happens

Trigger: host.set or host.compareAndSet where (currentTotalBytes - existingSizeForKey + newSize) > quotaBytes. Concretely: writing many keys whose SUM(size) passes 5 MB, replacing a small key with a much larger value that crosses the threshold, or writing a single multi-MB blob.

Common situations: A plugin accumulates unbounded history/cache rows over time and eventually crosses 5 MB; a plugin stores large base64 blobs or serialized images; the default quota was not raised for a data-heavy plugin. Note the check is per-pluginId, not per-key, so many small keys add up.

Related errors


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