fix(install): honor frontmatter provider with flag > frontmatter > default precedence (fixes #414)#431
Conversation
…fault precedence (fixes awslabs#414) `cao install <profile.md>` silently ignored the profile's frontmatter `provider:` key: the Click --provider option defaulted to kiro_cli and install_service.install_agent branched on that argument alone, so a profile declaring `provider: codex` installed as a kiro_cli agent with exit 0 and no warning. Install was the only entry point ignoring frontmatter — launch/assign/handoff already resolve it via resolve_provider(). The install provider now resolves with the same precedence as the launch paths: explicit --provider flag > frontmatter `provider:` > DEFAULT_PROVIDER. - CLI: --provider defaults to None; help text documents the precedence; the summary line echoes the provider the service actually resolved. - install_service.install_agent: accepts provider=None; explicit bad providers still fail fast BEFORE any URL download or env mutation; a bogus frontmatter provider logs a warning (same wording as resolve_provider) and falls back to the default. InstallResult gains a `provider` field carrying the resolved winner. - API: InstallAgentProfileRequest.provider defaults to None. - ops-MCP install_profile: provider defaults to None and is omitted from the request body when not explicit, so all three entry points behave identically. Built-in store profiles carry no frontmatter provider and keep today's default-provider behavior. Test matrix: flag wins over frontmatter; frontmatter wins when the flag is absent; both absent -> DEFAULT_PROVIDER; invalid frontmatter provider warns and falls back; explicit invalid --provider fails before download; built-in (no frontmatter) keeps the default; API and ops-MCP forward None for frontmatter resolution.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #431 +/- ##
=======================================
Coverage ? 88.41%
=======================================
Files ? 137
Lines ? 16385
Branches ? 0
=======================================
Hits ? 14486
Misses ? 1899
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
gutosantos82
left a comment
There was a problem hiding this comment.
PR #431 — fix(install): honor frontmatter provider with flag > frontmatter > default precedence
- Author: call-me-ram · Base: main · Head:
8d88a48 - Size: +266 / −14 across 9 files (5 src incl. CHANGELOG, 4 test)
- Linked issue: #414 (confirmed open; PR implements exactly the fix outline proposed and validated there)
- Mergeable: MERGEABLE · CI: pending/at head
What it does
Root cause (verified against upstream/main): cli/commands/install.py gave --provider
a Click default of DEFAULT_PROVIDER, and install_service.install_agent branched on that
argument alone — the parsed profile's provider frontmatter was never read on the install
path, unlike launch/assign/handoff which resolve it via resolve_provider(). A profile
declaring provider: codex installed silently as a kiro_cli agent with exit 0.
The fix, per entry point:
- CLI (
install.py) —--providerdefault becomesNone; help text documents the
precedence; the final summary line echoesresult.provider(the winner the service
actually resolved) with a safe fallback chain for older mocks. - Service (
install_service.py) —install_agent(provider: Optional[str] = None).
Explicit providers are still validated up front (fail-fast before URL download or
env-file mutation — verified by test withmock_get.assert_not_called()). WhenNone,
after the profile parse: valid frontmatter provider wins; invalid frontmatter logs a
warning (wording matchesresolve_provider()verbatim — checked side by side) and falls
back toDEFAULT_PROVIDER.InstallResultgains anOptional[str] providerfield
(backward-compatible). - REST API (
api/main.py) —InstallAgentProfileRequest.provider: Optional[str] = None,
forwarded as-is to the service. - ops-MCP (
ops_mcp_server/server.py) —install_profiledefaultsprovider=Noneand
omits the key from the request body when not explicit (same pattern as_launch_session_impl).
The now-unusedDEFAULT_PROVIDERimport is removed (verified no remaining references in
that file).
Verification performed
- Read the full diff (489 lines, all 9 files) and the PR-head versions of the four
touched source files. - Ordering check: nothing reads
providerbetween the up-front explicit validation and
the post-parse frontmatter resolution; resolution happens before any provider-config
materialization or agent-file write. No stale-Nonepath exists. - Caller audit: grep across
src/confirmsinstall_agent()is called only from the CLI
and the API endpoint — both updated. In-session handoff/assign useresolve_provider,
untouched. - Import check:
OptionalandDEFAULT_PROVIDERare properly imported in the CLI module;
no leftoverDEFAULT_PROVIDERusage in the ops-MCP server after the import removal. - Ran the tests at the PR head (worktree + repo venv): all 4 touched test files —
104 passed, 5 failed; the same 5 failures (test_install_from_local_store_writes_copilot_config- 4
TestInstallSkillCatalogBakingtests) reproduce identically onupstream/main, so
they are pre-existing/environmental, not introduced by this PR. All 11 new tests pass.
- 4
- Warning-wording claim verified: the frontmatter-invalid warning is byte-identical in
format toresolve_provider()'s.
Test coverage assessment
The precedence matrix is fully covered: flag>frontmatter, frontmatter-when-no-flag,
double-absent→default, invalid frontmatter→warn+default (with caplog assertion), explicit
invalid→fail-fast-before-download, built-in profile default, plus entry-point forwarding
tests for CLI (None passed, resolved provider echoed), API (provider=None forwarded),
and ops-MCP (key omitted from body). One help-text test is trivial but harmless.
Nits (none blocking)
- The CLI echo fallback
result.provider or provider or DEFAULT_PROVIDERis slightly
defensive (the service always setsprovideron success now), but the comment explains
it's for older mocks — acceptable. frontmatter providervalidation intentionally happens after a URL download (warn+fallback
is not fail); this matches the issue's agreed design.
Publish-guard assessment
Touches ops_mcp_server/server.py and services/install_service.py — the sensitive-path
regex may HOLD an auto-approve (core service surface). The change does not touch auth,
credentials, tool-permission/--yolo, providers/ status detection, or release/CI files.
Diff is 280 lines / 9 files — under the >400-line gate. Behavior change is opt-in-shaped:
callers passing an explicit provider are unaffected; only the previously-silently-wrong
default path changes, in the direction users already expected. Low blast radius.
There was a problem hiding this comment.
Pull request overview
Aligns cao install with the provider-resolution precedence already used by launch/assign/handoff paths: explicit --provider flag > profile frontmatter provider: > DEFAULT_PROVIDER, preventing silent installs to the wrong provider.
Changes:
- Make CLI
--providerdefault toNone, document precedence in help, and echo the resolved provider in output. - Update
install_service.install_agent()to resolve provider using flag/frontmatter/default precedence and return the resolved provider inInstallResult. - Propagate
provider=Nonebehavior through API and ops-MCP entry points; add targeted tests and a changelog entry.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/services/test_install_service.py | Adds precedence/validation tests for provider resolution in install_service. |
| test/ops_mcp_server/test_server.py | Ensures ops-MCP install_profile omits provider when not explicitly provided. |
| test/cli/commands/test_install.py | Verifies CLI passes None when --provider is omitted and echoes resolved provider. |
| test/api/test_api_profiles.py | Verifies API forwards provider=None when omitted so service resolves frontmatter. |
| src/cli_agent_orchestrator/services/install_service.py | Implements provider resolution precedence and returns resolved provider in InstallResult. |
| src/cli_agent_orchestrator/ops_mcp_server/server.py | Changes MCP tool default provider behavior to omit unless explicitly set. |
| src/cli_agent_orchestrator/cli/commands/install.py | Sets --provider default to None, updates help text, prints resolved provider. |
| src/cli_agent_orchestrator/api/main.py | Makes request provider optional to enable frontmatter resolution in install path. |
| CHANGELOG.md | Adds Unreleased “Fixed” entry for honoring frontmatter provider on install. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # claude_code installs write a context file only — no kiro config, | ||
| # which is exactly what the pre-fix behaviour would have produced. |
Summary
cao installnow resolves the target provider with the same precedence the launch/assign/handoff paths already use: explicit--providerflag > profile frontmatterprovider:>DEFAULT_PROVIDER. Fixes #414. Thanks @chaogebaba for the report and for validating the precedence approach.Root cause
The Click
--provideroption defaulted toDEFAULT_PROVIDER(kiro_cli) andinstall_service.install_agentbranched on that argument alone, so a profile declaringprovider: codexinstalled silently as a kiro_cli agent (exit 0, no warning). Install was the only entry point ignoring frontmatter — launch/assign/handoff/API resolve it viaresolve_provider().Fix
--providerdefaults toNone; help text documents the precedence; the install summary echoes the provider the service actually resolved.install_agentacceptsprovider=None; an explicit bad provider still fails fast before any URL download or env-file mutation; a bogus frontmatter provider logs a warning (same wording asresolve_provider) and falls back to the default.InstallResultgains aproviderfield with the resolved winner (so the CLI summary can't printNone; backward-compatible, Optional).InstallAgentProfileRequest.providerand the ops-MCPinstall_profiletool default toNone(provider omitted from the request body when not explicit, same pattern as_launch_session_impl), so all three entry points behave identically. Built-in store profiles carry no frontmatter provider and keep today's default-provider behavior.Tests
DEFAULT_PROVIDER--provider→ fail-fast before download (existing behavior preserved)Nonefor identical frontmatter resolutiontest_settings_service.py::TestGetServerSettings) reproduces on a cleanupstream/maincheckout and is unrelated.black/isortclean.Fixes #414