{"record":{"id":"d27c893aadc0dea7","repo":"agalwood/Motrix","slug":"pluginruntimefault-d27c89","errorCode":"PluginRuntimeFault","errorMessage":"plugin.ffmpeg.staging_quota_exceeded","messagePattern":"plugin\\.ffmpeg\\.staging_quota_exceeded","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/plugin/hooks/staging-dir.ts","lineNumber":66,"sourceCode":"  }\n\n  async ensureDir(): Promise<void> {\n    await mkdir(this.dir, { recursive: true })\n  }\n\n  async assertQuota(): Promise<void> {\n    let total = 0\n    const entries = await readdir(this.dir, {\n      recursive: true,\n      withFileTypes: true,\n    })\n    for (const e of entries) {\n      if (e.isFile()) {\n        total += (await stat(path.join(e.parentPath, e.name))).size\n      }\n    }\n    if (total > this.opts.quotaBytes) {\n      throw new AppError(\n        ErrorCode.PluginRuntimeFault,\n        'plugin.ffmpeg.staging_quota_exceeded'\n      )\n    }\n  }\n\n  /**\n   * Chain commit: promote the file matching `finalFilePath` to saveDir; delete\n   * all remaining staging contents afterward.\n   *\n   * `finalFilePath` may be absolute (resolving to saveDir) or relative to\n   * saveDir. Returns the absolute path of the promoted file.\n   */\n  async promote(finalFilePath: string): Promise<string> {\n    const saveDir = this.opts.saveDir.replace(/[/\\\\]+$/, '')\n    const finalAbs = path.resolve(saveDir, finalFilePath)\n    const stagedAbs = path.join(this.dir, path.relative(saveDir, finalAbs))\n    const tmp = `${finalAbs}.tmp-${Date.now()}`","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/hooks/staging-dir.ts#L48-L84","documentation":"Thrown by FfmpegStaging.assertQuota() when the recursive sum of file sizes under the staging directory exceeds opts.quotaBytes. Enforces a per-task disk budget on ffmpeg output before chain commit. Tagged PluginRuntimeFault because the plugin produced more output than its configured allowance.","triggerScenarios":"A beforeFinalize plugin runs ffmpeg whose output lands in the staging dir; assertQuota() is called (pre-commit) and total bytes > quotaBytes. The loop at staging-dir.ts:54-64 stats every file recursively before the check at line 65.","commonSituations":"quotaBytes configured too low (default example is 4 GiB); plugin transcodes to a high bitrate/large format; multiple staged files accumulate because discard() was not called after a prior failed run; orphaned staging files from a crashed process (cleanupOrphans only runs at startup).","solutions":["Raise quotaBytes in the StagingOptions passed to FfmpegStaging (e.g. from 1 GiB to 4 * 1024 ** 3).","Reduce ffmpeg output size: lower bitrate, shorter duration, fewer streams, or a more compact container.","Ensure discard() is invoked on hook failure so staging does not accumulate across retries.","Call FfmpegStaging.cleanupOrphans(pluginsDir) at host startup to reap leftover staging dirs from crashed runs."],"exampleFix":"// before\nnew FfmpegStaging({ ..., quotaBytes: 1 * 1024 ** 3 })\n// after\nnew FfmpegStaging({ ..., quotaBytes: 4 * 1024 ** 3 })","handlingStrategy":"try-catch","validationCode":"import { stat } from 'node:fs/promises'\nimport path from 'node:path'\n// Pre-check staging size before triggering assertQuota.\nasync function projectedBytes(stagingDir: string): Promise<number> {\n  let total = 0\n  for (const e of await readdir(stagingDir, { recursive: true, withFileTypes: true })) {\n    if (e.isFile()) total += (await stat(path.join(e.parentPath, e.name))).size\n  }\n  return total\n}","typeGuard":null,"tryCatchPattern":"try {\n  await staging.assertQuota()\n} catch (e) {\n  if (e instanceof AppError && e.message === 'plugin.ffmpeg.staging_quota_exceeded') {\n    // lower ffmpeg quality / duration, or raise quotaBytes, then retry\n    await staging.discard() // free the staging dir\n    throw e\n  }\n  throw e\n}","preventionTips":["Tune quotaBytes to the largest legitimate single-task transcode plus headroom.","Always call discard() in a hook failure handler so staging never accumulates.","Run FfmpegStaging.cleanupOrphans(pluginsDir) at host startup to recover from crashes."],"tags":["plugin","ffmpeg","disk-quota","resource-limits"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}