agalwood/Motrix · error · AppError
PluginRuntimeFault
PluginRuntimeFault
Error message
plugin.ffmpeg.staging_quota_exceeded
What it means
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.
Source
Thrown at src/core/plugin/hooks/staging-dir.ts:66
}
async ensureDir(): Promise<void> {
await mkdir(this.dir, { recursive: true })
}
async assertQuota(): Promise<void> {
let total = 0
const entries = await readdir(this.dir, {
recursive: true,
withFileTypes: true,
})
for (const e of entries) {
if (e.isFile()) {
total += (await stat(path.join(e.parentPath, e.name))).size
}
}
if (total > this.opts.quotaBytes) {
throw new AppError(
ErrorCode.PluginRuntimeFault,
'plugin.ffmpeg.staging_quota_exceeded'
)
}
}
/**
* Chain commit: promote the file matching `finalFilePath` to saveDir; delete
* all remaining staging contents afterward.
*
* `finalFilePath` may be absolute (resolving to saveDir) or relative to
* saveDir. Returns the absolute path of the promoted file.
*/
async promote(finalFilePath: string): Promise<string> {
const saveDir = this.opts.saveDir.replace(/[/\\]+$/, '')
const finalAbs = path.resolve(saveDir, finalFilePath)
const stagedAbs = path.join(this.dir, path.relative(saveDir, finalAbs))
const tmp = `${finalAbs}.tmp-${Date.now()}`View on GitHub (pinned to 1a708ee577)
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.
Example fix
// before
new FfmpegStaging({ ..., quotaBytes: 1 * 1024 ** 3 })
// after
new FfmpegStaging({ ..., quotaBytes: 4 * 1024 ** 3 }) Defensive patterns
Strategy: try-catch
Validate before calling
import { stat } from 'node:fs/promises'
import path from 'node:path'
// Pre-check staging size before triggering assertQuota.
async function projectedBytes(stagingDir: string): Promise<number> {
let total = 0
for (const e of await readdir(stagingDir, { recursive: true, withFileTypes: true })) {
if (e.isFile()) total += (await stat(path.join(e.parentPath, e.name))).size
}
return total
} Try / catch
try {
await staging.assertQuota()
} catch (e) {
if (e instanceof AppError && e.message === 'plugin.ffmpeg.staging_quota_exceeded') {
// lower ffmpeg quality / duration, or raise quotaBytes, then retry
await staging.discard() // free the staging dir
throw e
}
throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- PluginActivationCapExceeded
- plugin.ffmpeg.destination_phase_disallowed
- plugin.ffmpeg.op_not_found
- unsupported-kind
- plugin.commands.id_out_of_namespace
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/d27c893aadc0dea7.
Report an issue: GitHub.