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

  1. 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.
  2. Gate the mutation call on role !== 'audit' before submitting the patch.
  3. If the role assignment is wrong, correct the role-band configuration for that plugin/hook.
  4. 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

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


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