Description
A project-local .bmad-loop/plugins/<name>/plugin.toml that fails to parse raises PluginError, deliberately: tests/test_plugin_loader.py::test_invalid_manifest_rejected and ::test_invalid_toml_rejected pin it. I am not asking for that to change.
The gap is that nothing diagnoses it before a run. bmad-loop validate covers config, policy, the story queue, git, platform, adapters, hooks and skills, but never loads the plugin tree — there is no load_plugins or discover call in cli.py. run --dry-run returns at cli.py:1999 before run composition, so it is not a check either. The first thing that reads the manifest is PluginRegistry.build inside Engine.__init__ (registry.py:129, engine.py:659), reached from compose_run after the run directory, state, pid and the run-start journal entry are already published (runsetup.py:1018-1081); composition then unwinds.
So a one-character typo in a plugin manifest is invisible to the command an operator runs to check their setup, and surfaces only when a real run is started — repeatedly, since every run fails the same way until it is fixed.
Steps to reproduce
mkdir -p .bmad-loop/plugins/broken && printf 'description = "x"\n' > .bmad-loop/plugins/broken/plugin.toml
bmad-loop validate — no finding mentions the plugin.
bmad-loop run --dry-run --story <id> — no finding mentions the plugin.
- Start a real run.
Expected behavior
validate reports the manifest error, without importing or executing plugin code — manifests are already read that way elsewhere (settings_schema.py:258 builds the settings surface from load_plugins and explicitly never imports plugin Python).
Actual behavior
Steps 2 and 3 say nothing. Step 4 fails with:
PluginError: plugin <project>/.bmad-loop/plugins/broken/plugin.toml: missing [plugin] table
Which area is this for?
Plugins
bmad-loop Version
0.11.1
Environment
macOS (Darwin 25.5), coding CLI claude. Observed while writing a project-local plugin: two malformed drafts each surfaced only at run time. Verified against main @ c47333d1. Searched open and closed issues for plugin / manifest / validate — #689 and #688 standardized the error type for unreadable manifests but added no preflight, and I did not find this filed. Happy to attach a diagnose dump, though the reproduction above is self-contained.
Description
A project-local
.bmad-loop/plugins/<name>/plugin.tomlthat fails to parse raisesPluginError, deliberately:tests/test_plugin_loader.py::test_invalid_manifest_rejectedand::test_invalid_toml_rejectedpin it. I am not asking for that to change.The gap is that nothing diagnoses it before a run.
bmad-loop validatecovers config, policy, the story queue, git, platform, adapters, hooks and skills, but never loads the plugin tree — there is noload_pluginsordiscovercall incli.py.run --dry-runreturns atcli.py:1999before run composition, so it is not a check either. The first thing that reads the manifest isPluginRegistry.buildinsideEngine.__init__(registry.py:129,engine.py:659), reached fromcompose_runafter the run directory, state, pid and therun-startjournal entry are already published (runsetup.py:1018-1081); composition then unwinds.So a one-character typo in a plugin manifest is invisible to the command an operator runs to check their setup, and surfaces only when a real run is started — repeatedly, since every run fails the same way until it is fixed.
Steps to reproduce
mkdir -p .bmad-loop/plugins/broken && printf 'description = "x"\n' > .bmad-loop/plugins/broken/plugin.tomlbmad-loop validate— no finding mentions the plugin.bmad-loop run --dry-run --story <id>— no finding mentions the plugin.Expected behavior
validatereports the manifest error, without importing or executing plugin code — manifests are already read that way elsewhere (settings_schema.py:258builds the settings surface fromload_pluginsand explicitly never imports plugin Python).Actual behavior
Steps 2 and 3 say nothing. Step 4 fails with:
Which area is this for?
Plugins
bmad-loop Version
0.11.1
Environment
macOS (Darwin 25.5), coding CLI
claude. Observed while writing a project-local plugin: two malformed drafts each surfaced only at run time. Verified againstmain@c47333d1. Searched open and closed issues for plugin / manifest / validate — #689 and #688 standardized the error type for unreadable manifests but added no preflight, and I did not find this filed. Happy to attach adiagnosedump, though the reproduction above is self-contained.