agalwood/Motrix · error · Error

plugin.manifest.role.requires_builtin

Error message

plugin.manifest.role.requires_builtin

What it means

Thrown by bandIndex() in role-band.ts when role === 'pre-resolve' AND opts.builtin !== true (explicitly false). The 'pre-resolve' band is reserved for engine built-in plugins; third-party/user plugins may not claim it. Note: unlike most errors here this is a plain Error (no ErrorCode), surfaced during manifest validation/sorting.

Source

Thrown at src/core/plugin/hooks/role-band.ts:20

  | 'pre-resolve'
  | 'resolve'
  | 'enrich'
  | 'post-process'
  | 'audit'
const ORDER: RoleBand[] = [
  'pre-resolve',
  'resolve',
  'enrich',
  'post-process',
  'audit',
]

export function bandIndex(
  role: RoleBand,
  opts?: { builtin?: boolean }
): number {
  if (role === 'pre-resolve' && opts?.builtin === false)
    throw new Error('plugin.manifest.role.requires_builtin')
  const i = ORDER.indexOf(role)
  if (i < 0) throw new Error(`unknown role band: ${role}`)
  return i
}

export interface BandSortable {
  pluginId: string
  role: RoleBand
}
export function sortByBand<T extends BandSortable>(arr: ReadonlyArray<T>): T[] {
  return [...arr].sort((a, b) => {
    const da = bandIndex(a.role) - bandIndex(b.role)
    return da !== 0 ? da : a.pluginId.localeCompare(b.pluginId)
  })
}

export function isMostCritical(role: RoleBand): boolean {
  return role === 'pre-resolve' || role === 'resolve' || role === 'post-process'

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Change the plugin manifest role from 'pre-resolve' to 'resolve' (or another non-restricted band) — these are available to third-party plugins.
  2. If you are authoring a genuine engine builtin, ensure the loader marks the plugin builtin:true before bandIndex is called.
  3. Validate the role band at manifest-load time and reject 'pre-resolve' for user plugins with a clear message before it reaches bandIndex.

Example fix

// before — plugin.manifest.json (third-party)
{ "role": "pre-resolve" }
// after
{ "role": "resolve" }
Defensive patterns

Strategy: type-guard

Validate before calling

// During manifest validation, reject 'pre-resolve' for non-builtin plugins.
const USER_ALLOWED_ROLES = ['resolve','enrich','post-process','audit'] as const
function allowedRole(role: string, builtin: boolean): boolean {
  if (role === 'pre-resolve') return builtin === true
  return (USER_ALLOWED_ROLES as readonly string[]).includes(role)
}

Type guard

function isUserAllowedRole(role: string): role is 'resolve'|'enrich'|'post-process'|'audit' {
  return ['resolve','enrich','post-process','audit'].includes(role)
}

Prevention

When it happens

Trigger: A non-builtin plugin's manifest declares role 'pre-resolve' and the loader calls bandIndex(role, { builtin: false }) during manifest validation or sortByBand. Line 19-20 throws before the ORDER.indexOf lookup.

Common situations: Third-party plugin copies a builtin manifest verbatim; manifest author picks 'pre-resolve' thinking it runs first; loader regression that forgets to set builtin:true for engine plugins; plugin scaffold template shipped with the wrong default role.

Related errors


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