Describe your idea
Let a project override the executable a CLI profile spawns, without duplicating the whole profile:
- A
binary field in [adapter] (and [adapter.dev|review|triage]) in policy.toml.
- Per-run flags on
run/sweep: --cli <profile> and --cli-binary <executable>.
Why is this needed?
Users who hold more than one subscription/profile for the same coding CLI invoke it under different names — e.g. a work account launched as cw and a personal one as claude, or two aliases used to split usage limits. Today bmad-loop always spawns CLIProfile.binary, which is fixed per profile.
The overlay in .bmad-loop/profiles/*.toml can express this, but only by copying the entire packaged profile to change one line. That copy then silently freezes everything else: bypass_args, [env] (CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN, CLAUDE_CODE_DISABLE_BACKGROUND_TASKS), seed_files, and env_fault_patterns stop tracking upstream. A user who forked claude.toml before #194 landed would never have received the transport-fault patterns. The cost of changing one field is permanent drift on eight others.
Choosing the account per run is also not expressible at all: policy.toml is project-scoped state, so switching accounts means editing a file rather than passing a flag — awkward when alternating profiles on the same repo.
How should it work?
The seam already exists and is simply unwired: both GenericAdapter.__init__ (adapters/generic.py:417) and OpencodeHttpAdapter.__init__ (adapters/opencode_http.py:389) already accept binary: str | None = None and do self.binary = binary or profile.binary. Nothing passes it — only probe-adapter --binary uses a parallel path.
Proposed:
Policy field — AdapterPolicy.binary: str = "" and StageAdapterPolicy.binary: str | None = None, surfaced through ResolvedAdapter. ""/None = use the profile's binary, so the default is unchanged. Inheritance follows the existing model/extra_args rule in AdapterPolicy.resolved: client-specific, inherited from the base only when same_client, so a stage that switches profile doesn't carry the other CLI's executable. runsetup.make_adapters passes cfg.binary or None into the ctor that already takes it.
Per-run flags — --cli / --cli-binary on run and sweep, applied as a dataclasses.replace on the policy before the snapshot is stamped into RunState, so resume, the --json adapters projection (documents.py:255) and per-task adapter stamping all inherit the override with no extra threading.
Also needed — cmd_validate's adapter.binary check (cli.py:294) currently probes p.binary per profile; it has to probe the effective binary, otherwise validate reports "claude found" for a run that will spawn cc.
Constraint worth documenting rather than detecting: an override assumes the same hook dialect, hooks.config_path and transcript layout as the profile it overrides. It is for an alias/wrapper of the same CLI, not a way to point claude at a different tool.
Nothing here touches the control loop, adds an LLM call, or changes a completion path.
PR
I'd like to contribute this, split into two PRs so each is one logical change:
- the
[adapter] binary policy field + plumbing + the cmd_validate fix
- the
--cli / --cli-binary per-run flags, based on (1)
Happy to adjust the shape (or drop half of it) before I open anything — flagging first per CONTRIBUTING.md.
Additional context
Related surfaces I expect to touch: policy.py (dataclasses, _parse_policy, _stage_adapter, adapter_policy_from_snapshot, _stage_from_snapshot, the embedded policy template), data/settings/core.toml (the sync test enforces field parity), runsetup.py, cli.py, plus docs/FEATURES.md and the changelog.
Describe your idea
Let a project override the executable a CLI profile spawns, without duplicating the whole profile:
binaryfield in[adapter](and[adapter.dev|review|triage]) inpolicy.toml.run/sweep:--cli <profile>and--cli-binary <executable>.Why is this needed?
Users who hold more than one subscription/profile for the same coding CLI invoke it under different names — e.g. a work account launched as
cwand a personal one asclaude, or two aliases used to split usage limits. Today bmad-loop always spawnsCLIProfile.binary, which is fixed per profile.The overlay in
.bmad-loop/profiles/*.tomlcan express this, but only by copying the entire packaged profile to change one line. That copy then silently freezes everything else:bypass_args,[env](CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN,CLAUDE_CODE_DISABLE_BACKGROUND_TASKS),seed_files, andenv_fault_patternsstop tracking upstream. A user who forkedclaude.tomlbefore #194 landed would never have received the transport-fault patterns. The cost of changing one field is permanent drift on eight others.Choosing the account per run is also not expressible at all:
policy.tomlis project-scoped state, so switching accounts means editing a file rather than passing a flag — awkward when alternating profiles on the same repo.How should it work?
The seam already exists and is simply unwired: both
GenericAdapter.__init__(adapters/generic.py:417) andOpencodeHttpAdapter.__init__(adapters/opencode_http.py:389) already acceptbinary: str | None = Noneand doself.binary = binary or profile.binary. Nothing passes it — onlyprobe-adapter --binaryuses a parallel path.Proposed:
Policy field —
AdapterPolicy.binary: str = ""andStageAdapterPolicy.binary: str | None = None, surfaced throughResolvedAdapter.""/None= use the profile's binary, so the default is unchanged. Inheritance follows the existingmodel/extra_argsrule inAdapterPolicy.resolved: client-specific, inherited from the base only whensame_client, so a stage that switches profile doesn't carry the other CLI's executable.runsetup.make_adapterspassescfg.binary or Noneinto the ctor that already takes it.Per-run flags —
--cli/--cli-binaryonrunandsweep, applied as adataclasses.replaceon the policy before the snapshot is stamped intoRunState, soresume, the--jsonadaptersprojection (documents.py:255) and per-task adapter stamping all inherit the override with no extra threading.Also needed —
cmd_validate'sadapter.binarycheck (cli.py:294) currently probesp.binaryper profile; it has to probe the effective binary, otherwise validate reports "claude found" for a run that will spawncc.Constraint worth documenting rather than detecting: an override assumes the same hook dialect,
hooks.config_pathand transcript layout as the profile it overrides. It is for an alias/wrapper of the same CLI, not a way to pointclaudeat a different tool.Nothing here touches the control loop, adds an LLM call, or changes a completion path.
PR
I'd like to contribute this, split into two PRs so each is one logical change:
[adapter] binarypolicy field + plumbing + thecmd_validatefix--cli/--cli-binaryper-run flags, based on (1)Happy to adjust the shape (or drop half of it) before I open anything — flagging first per CONTRIBUTING.md.
Additional context
Related surfaces I expect to touch:
policy.py(dataclasses,_parse_policy,_stage_adapter,adapter_policy_from_snapshot,_stage_from_snapshot, the embedded policy template),data/settings/core.toml(the sync test enforces field parity),runsetup.py,cli.py, plusdocs/FEATURES.mdand the changelog.