agalwood/Motrix · error · AppError
PluginRuntimeFault
PluginRuntimeFault
Error message
AuditRoleCannotMutate
What it means
Thrown by validateHttpPatch (and validateFinalizePatch) when opts.role === 'audit'. The audit role is read-only by design: a plugin running in the audit role band must never mutate task context, so any patch submission is rejected before schema parsing even begins. Classified PluginRuntimeFault.
Source
Thrown at src/core/plugin/hooks/ctx-update.ts:48
export const FinalizePatchSchema = z
.object({
filePath: z.string(),
})
.strict()
export interface ValidateOptions {
permissions: ReadonlySet<string>
role: RoleBand
hook: 'beforeCreate' | 'beforeFinalize'
saveDir: string
}
export function validateHttpPatch(
patch: unknown,
opts: ValidateOptions
): z.infer<typeof HttpPatchSchema> {
if (opts.role === 'audit')
throw new AppError(ErrorCode.PluginRuntimeFault, 'AuditRoleCannotMutate')
const parsed = HttpPatchSchema.safeParse(patch)
if (!parsed.success)
throw new AppError(
ErrorCode.PluginRuntimeFault,
`CtxUpdateInvalid: ${parsed.error.issues[0]?.message ?? 'invalid patch'}`
)
if (
parsed.data.filename !== undefined &&
!opts.permissions.has('fs.task.write')
)
throw new AppError(
ErrorCode.PluginRuntimeFault,
'CtxUpdateInvalid: filename requires fs.task.write permission'
)
return parsed.data
}
export function validateFinalizePatch(View on GitHub (pinned to 1a708ee577)
Solutions
- Confirm the plugin's intended role — audit-role plugins must not issue context mutations; route the action through a plugin running in a writable role band instead.
- Gate the mutation call on role !== 'audit' before submitting the patch.
- If the role assignment is wrong, correct the role-band configuration for that plugin/hook.
- For audit-role plugins, restrict their API surface to read-only context access.
Example fix
// before
if (role === 'audit') validateHttpPatch(patch, { ...opts, role })
// after
if (role !== 'audit') validateHttpPatch(patch, { ...opts, role })
else read-only-context-access() Defensive patterns
Strategy: validation
Validate before calling
if (opts.role === 'audit') { /* read-only; do not call validateHttpPatch with a mutation */ } Type guard
function isWritableRole(role: RoleBand): boolean { return role !== 'audit' } Try / catch
try { validateHttpPatch(patch, opts) }
catch (e) { if (e.message === 'AuditRoleCannotMutate') { /* route to a writable-role plugin */ } else throw e } Prevention
- Never assign mutation duties to audit-role plugins.
- Gate mutation calls on role !== 'audit'.
- Restrict audit-role plugin API surfaces to read-only context access.
When it happens
Trigger: A plugin whose current role band is 'audit' calls the context-update hook (beforeCreate/beforeFinalize) with an HTTP patch. The very first check in validateHttpPatch inspects opts.role and throws immediately.
Common situations: A plugin designed for a read/write role is mistakenly assigned or escalated to the audit role; a hook dispatcher routes a mutation request from an audit-role plugin; or a developer tests a mutation path against a plugin configured as auditor.
Related errors
- plugin.commands.access_denied
- PluginRuntimeFault
- PluginRuntimeFault
- PluginPermissionUnsupported
- plugin.ffmpeg.destination_phase_disallowed
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/32b8cc321c058d56.
Report an issue: GitHub.