Skip to content

[BUG] validate and run blocked: bmm >=6.10.1 renamed the dev primitive to bmad-build-auto, leaving bmad-dev-auto a shim #393

Description

@tjoignant

Description

On a project running BMad Method 6.10.1-next.34, bmad-loop validate fails and bmad-loop run aborts before spawning any session. bmm renamed the dev primitive bmad-dev-autobmad-build-auto and left bmad-dev-auto behind as a deprecation shim (a lone SKILL.md that forwards). bmad-loop 0.9.0 still targets the old name and content-probes it for marker files that now live under the new name.

There is no way to configure around it, and every released tag predates the rename, so the loop is unrunnable on current bmm.

Steps to reproduce

  1. Install bmm 6.10.1-next.34 in a project (npx bmad-method install), with the claude skill tree.
  2. uv tool install "bmad-loop[tui] @ git+https://github.com/bmad-code-org/bmad-loop.git" (resolves to main @ 5c86f98, version 0.9.0).
  3. bmad-loop init --project . --cli claude — succeeds; hooks, skills, policy all fine.
  4. bmad-loop validate --project .

Expected behavior

Preflight recognises the project's dev primitive and passes, so run can start. bmm ships a complete dev primitive here — it is simply called bmad-build-auto, and both marker files bmad-loop demands are present in it.

Actual behavior

FAIL: .claude/skills/bmad-dev-auto is incomplete (missing step-04-review.md, customize.toml) — reinstall it from the bmm module

Every other check passes (config, policy, sprint-status, git, tmux, process host, claude found, hooks registered). The suggested remediation cannot fix it: reinstalling bmm re-renders the same shim, by design.

Critically, this is not validate-only. missing_base_skills returns severity problem; _require_base_skills (cli.py:494) maps that to False, which gates run at cli.py:912, plus cli.py:1144 and cli.py:1199. The run aborts before any session spawns.

Analysis

bmm 6.10.1 moved the primitive and shimmed the old name. From the project's _bmad/_config/skill-manifest.csv:

"bmad-build-auto","bmad-build-auto","One iteration of an unattended development loop. Use when invoked by name.","bmm","_bmad/bmm/4-implementation/bmad-build-auto/SKILL.md"
"bmad-dev-auto","bmad-dev-auto","Deprecated: forwards to bmad-build-auto. Do not use unless invoked by name.","bmm","_bmad/bmm/v6-shims/bmad-dev-auto/SKILL.md"

Note the source path on the second row: bmm/v6-shims/. On disk:

.claude/skills/bmad-dev-auto/     -> SKILL.md only (the forwarding shim)
.claude/skills/bmad-build-auto/   -> SKILL.md, customize.toml, workflow.md, spec-template.md,
                                     compile-epic-context.md, step-01-clarify-and-route.md,
                                     step-02-plan.md, step-03-implement.md, step-04-review.md,
                                     references/, review-prompts/

The old name is hardcoded in five places:

Location Code Effect
install.py:107 DEV_PRIMITIVE_SKILL = "bmad-dev-auto" missing_base_skills probes the shim dir
install.py:84 DEV_BASE_SKILLS = {"bmad-dev-auto": ("step-04-review.md", "customize.toml"), …} markers absent from the shim → problem finding
install.py:350 STORIES_PROBE_SKILL = "bmad-dev-auto" stories mode's folder+id probe reads bmad-dev-auto/step-01-clarify-and-route.md — also only in bmad-build-auto now, so stories mode hits the same wall
policy.py:41,861 DEV_SKILLS = {"bmad-dev-auto"}; raise PolicyError otherwise dev.skill cannot be repointed
engine.py:1996, runsetup.py:83 policy.dev.skill == "bmad-dev-auto" selects GenericDevAdapter result synthesis

Why there's no workaround

  • Config can't repoint it. dev.skill = "bmad-build-auto" raises PolicyError at policy.py:861. Even if the allowlist were widened by hand, engine.py:1996 / runsetup.py:83 compare against the literal "bmad-dev-auto" to decide whether the adapter synthesizes a result.json — so a bare allowlist change would silently disable result synthesis rather than fix anything.
  • No escape hatch. No --skip-preflight / --force on run; the only related env var is BMAD_LOOP_SKIP_REVIEW (engine.py:2765), which is unrelated.
  • No older tag helps. Every tag from v0.7.5 through v0.9.0 predates the bmm rename, so they all target bmad-dev-auto too.

Why relaxing the marker check alone is not sufficient

Dispatch is by skill name (/bmad-dev-auto <story key>, cli.py:997-998, cli.py:1043), so with the preflight merely satisfied the dev pass would land on the shim and reach the real primitive only through LLM-mediated forwarding. Two consequences:

  1. A deadlock seam in an unattended session. The shim's migration path instructs the session to "HALT and do not invoke any skill" if approval to rename a legacy _bmad/custom/bmad-dev-auto.toml / .user.toml is "declined or unavailable". An automated bmad-loop session has no human to approve, so any project carrying a legacy customize file under the old name would hang instead of running. (Not my case — I have no such file — but it is a live hazard for anyone who customized bmad-dev-auto.)
  2. Customization keys off the directory name. _bmad/custom/*.toml overrides are keyed by skill directory, so overrides for the primitive now belong to bmad-build-auto. A tool dispatching bmad-dev-auto and a project customizing bmad-build-auto disagree about which file is authoritative.

Targeting bmad-build-auto directly avoids both.

Suggested fix

Resolve the dev primitive by preference rather than by constant: use bmad-build-auto when its skill dir exists, else fall back to bmad-dev-auto — resolved per skill tree, since a project can have a post-rename .claude tree and a pre-rename .agents one side by side (the same reasoning missing_base_skills already applies to review-layer derivation). The resolved name then needs to flow through all five sites above: the marker probe, the stories probe, the dev.skill allowlist and its default, the GenericDevAdapter selection predicate, and the dispatch strings.

Secondary observation (possibly its own issue): bmm's bmad-build-auto/customize.toml now makes each [[workflow.review_layers]] self-contained — the layers read {skill-root}/review-prompts/{adversarial,edge-case-hunter,verification-gap}.md and explicitly instruct "Do not invoke any skill". So resolve_review_layers' derivation of required review skills from the primitive's config finds nothing to require, and missing_base_skills falls back to the static catalog (bmad-review-adversarial-general, bmad-review-edge-case-hunter, satisfied by a present bmad-review). It resolved cleanly for me, but the machinery is aimed at a skill shape upstream has moved past, and the fallback is doing the real work.

Which area is this for?

Orchestrator / control loop

bmad-loop Version

0.9.0, installed from main @ 5c86f98c7b13ce55947a2bd59d5c8d7861c57d9c (== git ls-remote HEAD at time of filing). Installed via uv tool install "bmad-loop[tui] @ git+…", Python 3.14.

BMad Method (bmm): 6.10.1-next.34 (_bmad/_config/manifest.yaml).

Which coding CLI are you using?

claude — all three roles (adapter dev=claude, review=claude, triage=claude), skill tree .claude/skills/. Sprint mode ([stories] source = "sprint-status").

Operating System

macOS (darwin 24.6.0), tmux 3.7b.

Relevant log output

$ bmad-loop validate --project .
FAIL: .claude/skills/bmad-dev-auto is incomplete (missing step-04-review.md, customize.toml) — reinstall it from the bmm module
  ok: BMAD config OK: artifacts at <project>/_bmad-output/implementation-artifacts
  ok: policy OK: gates=per-epic, adapter dev=claude, review=claude, triage=claude
  ok: sprint-status OK: 63 stories, 32 actionable
  ok: git worktree clean
  ok: multiplexer TmuxMultiplexer available (tmux 3.7b)
  ok: mux backends: tmux* (available, tmux 3.7b), psmux (unavailable)
  ok: process host: PosixProcessHost
  ok: claude found
  ok: bmad-loop hooks registered for claude

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions