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.

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.

Appendix: 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)