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
- Change the plugin manifest role from 'pre-resolve' to 'resolve' (or another non-restricted band) — these are available to third-party plugins.
- If you are authoring a genuine engine builtin, ensure the loader marks the plugin builtin:true before bandIndex is called.
- 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
- Document that 'pre-resolve' is builtin-only in the plugin authoring guide.
- Validate role at manifest load with a clear message rather than letting bandIndex throw.
- Keep the scaffold template's default role as 'enrich', never 'pre-resolve'.
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
- plugin.command.not_declared_in_manifest
- PluginManifestInvalid
- transient-failure
- plugin.commands.id_out_of_namespace
- plugin.commands.not_found
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/675b481b7454cb89.
Report an issue: GitHub.