fix(cli): add missing --yes flag to plan apply command #1127
Merged
hurui200320
merged 1 commits from 2026-03-26 07:50:10 +00:00
fix/m4-plan-apply-yes-flag into master
Labels
Clear labels
auto/needs-reevaluation
controller-managed
overdue
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Needs Feedback
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
Controller deferred this PR; awaiting Phase 6+ scope-evaluator or operator re-enablement.
Auto-agents controller manages this PR/issue (see tools/controller/deploy/RUNBOOK.md). Remove this label to abandon controller management.
PR blocked by an open issue dependency. Operator must close the dep (or remove the dependency link) before the merge driver can act. Auto-cleared by merge_drive when no open deps remain.
Most recent merge cycle hit CI timeout. Driver excludes this PR while last merge_cycle row is < 30 min old; label persists thereafter as visible history.
Currently being processed by an implementer worker.
Currently being processed by the merge driver.
Currently being processed by a reviewer worker.
Merge driver heartbeat stale; pipeline halted. Closed automatically on next clean tick.
Detected master commit violating the strict merge invariant. Tracked as an issue (not a PR label); kept here for label completeness.
In-cycle escalation: most recent attempt ran at the Tier 0 slot (`tier-0`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 1 slot (`tier-1`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 2 slot (`tier-2`). Slot's model defined in .opencode/models/tiers.yaml. Gated behind IMPLEMENTER_ESCALATION_TIER2_ENABLED.
In-cycle escalation: most recent attempt ran at the Tier -1 slot (`tier-min`). Slot's model defined in .opencode/models/tiers.yaml. Suffix is ``-min`` (not ``--1``) so the Forgejo UI reads naturally.
Tracking issues used by the AI Automation system for agents to communicate and report.
Rebase conflict needs LLM conflict-resolver.
Failing CI needs implementer attention.
Documenting a driver incident or rollback.
Reviewer has APPROVED this PR and no later REQUEST_CHANGES is outstanding. The merge driver requires this label to even consider a PR for merging. Set by the reviewer worker on APPROVE; cleared on REQUEST_CHANGES.
Train repeatedly lost master-tempo races. Driver excludes via merge_cycle until cooldown elapses; label persists as visible history.
Revert PR backing out an invariant violation. Fast-tracked through the merge driver.
Sentinel PR duplicated from upstream into a personal fork by tools/duplicate_prs_to_fork.py for pipeline testing. Lives only in the fork; the canonical pipeline never sees it.
No implementer activity for N days. Flagged for human review. Auto-cleared on next push to head branch.
Repeatedly fails on current master (>= 3 ci-fail-on-rebased-sha releases in 12 h). Excluded from driver until human triage.
A ticket in a blocked state and unable to complete until some other task is completed first.
Bounty
$100
A bounty of $100 for any open-source contributor who provides a MR that solves this issue
Bounty
$1000
A bounty of $1000 for any open-source contributor who provides a MR that solves this issue
Bounty
$10000
A bounty of $10000 for any open-source contributor who provides a MR that solves this issue
Bounty
$20
A bounty of $20 for any open-source contributor who provides a MR that solves this issue
Bounty
$2000
A bounty of $2000 for any open-source contributor who provides a MR that solves this issue
Bounty
$250
A bounty of $250 for any open-source contributor who provides a MR that solves this issue
Bounty
$50
A bounty of $50 for any open-source contributor who provides a MR that solves this issue
Bounty
$500
A bounty of $500 for any open-source contributor who provides a MR that solves this issue
Bounty
$5000
A bounty of $5000 for any open-source contributor who provides a MR that solves this issue
Bounty
$750
A bounty of $750 for any open-source contributor who provides a MR that solves this issue
MoSCoW
Could have
Could have feature in order to satisfy the epic/legendary.
MoSCoW
Must have
Must have feature in order to satisfy the epic/legendary.
MoSCoW
Should have
Should have feature in order to satisfy the epic/legendary.
There are questions in the ticket that can not be completed until the project owner provides clarity.
Points
1
1 man-hours worth of work for an expert with no learning curve.
Points
13
13 man-hours worth of work for an expert with no learning curve.
Points
2
2 man-hours worth of work for an expert with no learning curve.
Points
21
21 man-hours worth of work for an expert with no learning curve.
Points
3
3 man-hours worth of work for an expert with no learning curve.
Points
34
34 man-hours worth of work for an expert with no learning curve.
Points
5
5 man-hours worth of work for an expert with no learning curve.
Points
55
55 man-hours worth of work for an expert with no learning curve.
Points
8
8 man-hours worth of work for an expert with no learning curve.
Points
88
88 man-hours worth of work for an expert with no learning curve.
Priority
Backlog
This ticket has backlogged priority and is not to be worked on yet
Priority
CI Blocker
Critical priority issue that blocks CI/CD pipeline and prevents PR merges
Priority
Critical
The priority is critical
Priority
High
The priority is high
Priority
Low
The priority is low
Priority
Medium
The priority is medium
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
A ticket for learning a tool or technology that is needed to be able to do future planning and design.
State
Completed
The ticket has been fully implemented, completed, and merged with the source code. This label should only be applied once a ticket is closed.
State
Duplicate
A ticket that represents the same content as an existing ticket.
State
In Progress
A ticket that is actively being developed.
State
In Review
A ticket that has had some code completed to implement but is waiting to pass peer review and is not yet merged in.
State
Paused
This ticket's work started but wasn't finished. It's on hold (likely in a feature branch) and will be resumed later, either due to a blocker or a delay.
State
Unverified
All new tickets start in this state. A developer may set it to show the ticket is unverified. This means we haven't agreed to work on it. It will either move to a verified state or be closed as wontdo.
State
Verified
The issue has been verified by a developer as legitimate. It will be worked on and verified tickets are now considered part of the backlog.
State
Wont Do
This ticket has been decided it wont be done. This may mean the bug has been determined to not be real (cant verify) or the feature is one we have decided we dont want to adopt.
Type
Automation
Any edits or discussion about the AI automated coding system.
Type
Bug
Something that doesnt work as intended.
Type
Discussion
Anytime a ticket represents a discussion about a subject and doesnt fall into one of the other categories.
Type
Documentation
An error or improvement needed in the documentation.
Type
Epic
Any first tier epic. That is, an epic which contains only issues as children and will not have sub-epics.
Type
Feature
Some new functionality not present.
Type
Legendary
A type of Epic which will contain other Epics.
Type
Refactor
A code change that restructures existing code without changing its external behavior.
Type
Support
Someone needs help using the project.
Type
Task
A generic task that doesnt fit into the other type categories.
Type
Testing
Work exclusively focusing on fixing or expanding testing.
No Label
Type
Bug
Projects
Clear projects
No project
Assignees
aditya (Aditya Chhabra)
aleenaumair (Aleena Umair)
brent.edwards (Brent Edwards)
CoreRasurae (Luis Mendes)
drew (Drew Morris)
eugen.thaci (Eugen Thaci)
freemo (Jeffrey Phillips Freeman)
HAL9000 (HAL 9000)
HAL9001 (HAL9001)
hamza.khyari (Hamza Khyari)
hurui200320 (Rui Hu)
justin.morris
khird (Kyle Hird)
org.cleveragents
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Blocks
#932 fix(cli): add missing --yes flag to plan apply command
cleveragents/cleveragents-core
Reference: cleveragents/cleveragents-core#1127
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "fix/m4-plan-apply-yes-flag"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements ticket #932 by adding
--yes/-ysupport toplan lifecycle-apply(spec:agents plan apply [--yes|-y] <PLAN_ID>), preserving interactive safety by default while enabling non-interactive CI/script execution.Closes #932.
What this PR now includes
Core ticket implementation
--yes/-ytolifecycle_apply_planinsrc/cleveragents/cli/commands/plan.py.--yesis not provided.Apply changes for plan <ID>? [y/N]:.ValueError+ guarded catch-all) consistent with neighboring lifecycle commands.PlanPhase/ProcessingStateimports to module level perCONTRIBUTING.mdimport rules.Test/documentation updates (from earlier review cycles)
lifecycle-applyCLI reference docs indocs/reference/plan_cli.md.Cycle 7 fix (this update)
fix/m4-plan-apply-yes-flagonto latestorigin/master.robot/e2e/wf05_db_migration.robotplan lifecycle-apply ${plan_id} --format jsonplan lifecycle-apply --yes ${plan_id} --format jsonrc=1.Deferred / Known limitations
applycommand--yesforwarding behavior remains outside this ticket scope.Quality gates (latest rerun on rebased branch)
nox -e lintnox -e typechecknox -e unit_testsnox -e integration_testsnox -e e2e_testsnox -e coverage_reportNotes for reviewers
--yesin WF05 E2E apply step.e5faeff6edtoe144fcc019e144fcc019to28acb4eaaf28acb4eaafto3d7bf9c0f6Code Review Report -- PR #1127 (Issue #932)
Branch:
fix/m4-plan-apply-yes-flagScope: All code changes in the branch plus close connections to surrounding code
Review cycles: 4 global passes across all categories (bugs, test coverage, test flaws, performance, security, spec compliance, code quality)
Overall Assessment
The implementation correctly adds the
--yes/-yflag tolifecycle-apply, follows the established Typer Option pattern from sibling destructive commands, properly removes the@tdd_expected_failtag, and comprehensively updates all existing test call-sites. The new Behave scenarios cover flag recognition, prompt suppression, decline behavior, and accept behavior with mock service verification. The benchmarks and Robot tests are correctly updated.That said, 4 review cycles surfaced the following findings, organized by severity.
MEDIUM Severity
M1. Bug/Inconsistency --
typer.Abort()on user decline produces exit code 1File:
src/cleveragents/cli/commands/plan.py:2085When the user declines the confirmation prompt, the code raises
typer.Abort(), which produces exit code 1 and appends a spurious"Aborted."line to the output. A user declining a destructive action is not an error condition -- it is a normal, expected interaction.Compare with sibling commands in the same file:
lifecycle-apply(this PR)typer.Abort()rollbacktyper.Abort()correcttyper.Exit(0)applytyper.Exit(0)correctand the legacyapplyusetyper.Exit(0)-- the more appropriate pattern for a user-initiated cancellation. UsingAbort()means: (a) the output contains both"Apply cancelled."and"Aborted."(redundant/confusing), (b) exit code 1 can break scripts that check$?to detect real errors, and (c) inconsistency withcorrectand legacyapplyin the same file.Recommendation: Change line 2085 from
raise typer.Abort()toraise typer.Exit(0).M2. Test Coverage Gap -- Missing exit code assertion on decline scenario
File:
features/tdd_plan_apply_yes_flag.feature:28-32The "user declines" scenario does not assert the exit code:
All three other scenarios in the same feature file assert
the lifecycle-apply exit code should be 0. This asymmetry means the decline path's exit code (currently 1 fromtyper.Abort()) is untested. If M1 is fixed to usetyper.Exit(0), a test should verify exit code 0 on decline.Recommendation: Add
And the lifecycle-apply exit code should be 0(or the appropriate expected code) to the decline scenario.M3. Spec Compliance -- Acceptance criterion "summary of pending changes" not implemented
File:
src/cleveragents/cli/commands/plan.py:2082Issue #932's acceptance criteria states:
The current implementation shows only:
No summary of pending changes (files affected, insertions/deletions, affected projects) is displayed before the user confirms. The acceptance criterion checkbox
[x]is checked in the issue body, but the "summary of pending changes" part is not satisfied.Note: The specification's own example output (
docs/specification.mdline 13234) shows the summary after confirmation, not before, so the spec and the issue criteria are inconsistent with each other. The implementation matches the spec example but not the issue acceptance criteria.Recommendation: Either implement a pre-confirmation summary (e.g., plan phase, project names, artifact count) or update the issue acceptance criteria to clarify that the summary appears post-confirmation. Consider this deferrable to a follow-up if agreed.
LOW Severity
L1. Robustness -- Missing
except Exceptioncatch-all handlerFile:
src/cleveragents/cli/commands/plan.py:2107-2115The
lifecycle_apply_plancommand's exception handlers end atexcept CleverAgentsError. The neighboringlifecycle_execute_plancommand (line 1999) includes a catch-allexcept Exception as e:that produces a clean"[red]Unexpected error:[/red]"message. Without this, an unexpected exception (e.g.,TypeErrorfrom a service bug) would produce a raw traceback.Note: If a catch-all is added, it must re-raise
typer.Abortandtyper.Exitto avoid intercepting the decline path and other early-abort paths. The pattern used elsewhere in the codebase is:L2. Documentation -- Description text not updated
File:
docs/reference/plan_cli.md:148-153The synopsis line was updated to include
[--yes|-y], but the description still only says "Transition a plan from Execute to Apply phase." There is no mention of the confirmation prompt, what--yesdoes, or that Apply is destructive. Other commands in the same file have richer descriptions.L3. Code Quality -- Dead code
is not NoneguardsFile:
src/cleveragents/cli/commands/plan.py:2073, 2091service.get_plan(plan_id)at line 2072 never returnsNone-- it raisesNotFoundError(aCleverAgentsErrorsubclass) if the plan does not exist. Therefore theif pre_plan is not Nonechecks at lines 2073 and 2091 are unreachable dead code. They are defensive but misleading, suggestingNoneis a possible return value when the API contract guarantees it is not.L4. Test Coverage Gap -- No test for
--yesafter positional argumentAll test files
Every test invocation uses
["lifecycle-apply", "--yes", plan_id](flag before argument). No test exercises["lifecycle-apply", plan_id, "--yes"]. While Typer/Click supports both orderings, this alternate ordering is untested.L5. Test Coverage Gap -- No test for auto-select + interactive prompt
File:
features/tdd_plan_apply_yes_flag.featureAll TDD scenarios pass an explicit plan ID. No test anywhere exercises the combined path: no
plan_idgiven -> auto-select resolves one plan -> confirmation prompt appears -> user accepts/declines. All existing auto-select tests in other files pass--yes, skipping the prompt entirely.INFORMATIONAL
I1. Code Quality -- Duplicate
PlanPhaseimportPlanPhaseis imported at line 2045 (inside theif not plan_id:block) and again at line 2087 (unconditionally). The second import is necessary (for whenplan_idis provided directly), but the visual redundancy could be eliminated by hoisting the import to the top of thetryblock.I2. Code Quality --
typer.confirmwithout explicitdefault=FalseLine 2082 uses
typer.confirm(...)withoutdefault=False. The implicit default isFalse(same behavior), butsession deleteandskill removespecify it explicitly for readability. Minor inconsistency.I3. Test Design -- Robot helper only tests flag recognition
robot/helper_tdd_plan_apply_yes_flag.pyusesDUMMY_PLAN_ID(not a valid ULID), so the command fails after flag parsing. This is intentional (the helper only tests that--yes/-yare recognized), but it provides no assurance about the correctness of the--yescode path beyond argument parsing.Summary
The core implementation is sound and the fix achieves its primary goal. M1 (Abort vs Exit on decline) is the most actionable finding as it affects script compatibility and output cleanliness. M2 follows naturally from M1. M3 may be deferrable depending on team agreement on the acceptance criteria interpretation.
@@ -151,3 +151,3 @@```bashagents plan lifecycle-apply [PLAN_ID] [--format FORMAT]agents plan lifecycle-apply [--yes|-y] [PLAN_ID] [--format FORMAT][L2] The synopsis was updated to include
[--yes|-y], but the description text (line 150) still only says "Transition a plan from Execute to Apply phase." Consider expanding to mention the confirmation prompt and the purpose of--yes.✅ Fixed. Expanded the description to: "Transition a plan from Execute to Apply phase. Because Apply is a destructive operation (it merges sandbox changesets into real project resources), a confirmation prompt is displayed by default. Pass
--yes/-yto skip the prompt in scripts or CI pipelines."@@ -23,0 +29,4 @@Given a plan CLI runner for the yes-flag testWhen I invoke lifecycle-apply without --yes and declineThen the lifecycle-apply output should contain "Apply cancelled."And the lifecycle-apply should not have called apply[M2] This scenario does not assert the exit code. All three other scenarios in this feature assert
the lifecycle-apply exit code should be 0. The decline path currently exits with code 1 (fromtyper.Abort()), but this is untested.✅ Fixed. Added
And the lifecycle-apply exit code should be 0to the decline scenario. This assertion now passes because M1 was fixed to usetyper.Exit(0)instead oftyper.Abort().[L1] Unlike the neighboring
lifecycle_execute_plan(line 1999), this command has noexcept Exceptioncatch-all. Unexpected exceptions will produce raw tracebacks instead of clean[red]Unexpected error:[/red]messages.If adding a catch-all, remember to re-raise
typer.Abortandtyper.Exit:✅ Fixed. Added the catch-all handler using exactly this pattern, matching
lifecycle_execute_plan. Theisinstancecheck ensurestyper.Abortandtyper.Exit(including the newtyper.Exit(0)from the decline path) pass through cleanly.[L3]
get_plan()never returnsNone-- it raisesNotFoundError. Thisis not Noneguard is dead code (always True). Same applies to line 2091. Defensive but misleading.✅ Fixed. Removed both
is not Noneguards. Verified thatPlanLifecycleService.get_plan()has return typePlan(notOptional[Plan]) and raisesNotFoundErrorif the plan doesn't exist.@@ -2069,0 +2079,4 @@# Confirmation prompt — Apply is destructive (merges sandbox into# real resources). Skip when --yes / -y is provided.if not yes:confirm = typer.confirm(f"\nApply changes for plan {plan_id}?")[M3] Issue #932 acceptance criteria says the prompt should show "the plan ID and a summary of pending changes." Currently only the plan ID is shown. The spec example shows the summary after confirmation, so there is ambiguity. Consider either adding a pre-confirmation summary or clarifying the acceptance criteria.
⏸️ Deferred. The spec example in
docs/specification.mdshows"Apply changes for plan <ID>? [y/N]: y"without a pre-confirmation summary — the summary appears after confirmation in the spec output. The implementation matches the spec example. This is a ticket-vs-spec ambiguity that should be resolved with the ticket author. Noted in PR description as a known limitation.@@ -2069,0 +2082,4 @@confirm = typer.confirm(f"\nApply changes for plan {plan_id}?")if not confirm:console.print("[yellow]Apply cancelled.[/yellow]")raise typer.Abort()[M1]
typer.Abort()here produces exit code 1 and appends"Aborted."to output. User declining a confirmation is not an error.correct_decisionand legacyapplyin this same file usetyper.Exit(0)for the decline path, which is the more appropriate pattern.Consider:
raise typer.Exit(0)✅ Fixed. Changed to
raise typer.Exit(0). Also addeddefault=Falseto thetyper.confirm()call for consistency with sibling commands (I2). The decline scenario now exits cleanly with code 0 and no spurious "Aborted." message.3d7bf9c0f6to759d4eca7fReview Response — Cycle 3
Thanks for the thorough review @CoreRasurae. All findings were verified against the code. Here's the summary:
✅ Addressed (8 items)
typer.Abort()→typer.Exit(0)on user decline — consistent withcorrect_decisionand legacyapplyexcept Exceptioncatch-all matchinglifecycle_execute_planpatternplan_cli.mddescription to mention confirmation prompt and--yespurposeis not Noneguards —get_plan()raisesNotFoundError, never returnsNonePlanPhase/ProcessingStateimport to top oftryblock, eliminating duplicatedefault=Falsetotyper.confirm()for consistency⏸️ Deferred (1 item)
ℹ️ Acknowledged, no action (3 items)
Additional
master(a854de7e)759d4eca7fto41a7c02b12Review: APPROVED
Excellent fix with thorough test coverage. The core change adds
--yes/-yTyper option with proper confirmation prompt and abort flow. TheValueErrorexception handler for provider/config resolution errors is a good defensive catch, and the catch-allexcept Exceptionwith explicit re-raise fortyper.Abort/typer.Exitprevents traceback leaks. BDD expanded from 2 to 5 scenarios, and all 13 existing test files touchinglifecycle-applyproperly updated to pass--yes. TDD@tdd_expected_failtag correctly removed. Well done.Minor Note
The import of
PlanPhase/ProcessingStatewas moved from inside anifblock but still sits inside the function body rather than at module level. Per CONTRIBUTING.md §Import Guidelines: "Ensure all imports are at the top of the Python file." Very minor style inconsistency.41a7c02b12to70ae83ea5970ae83ea59tod82c432029New commits pushed, approval review dismissed automatically according to repository settings
Review Response — Cycle 5 (Jeff's approval note)
Thanks for the approval and the import note @freemo.
✅ Addressed
PlanPhaseandProcessingStatefrom function-local import (insidelifecycle_apply_planbody) to module-level import at top ofplan.py, per CONTRIBUTING.md §Import Guidelines: "Ensure all imports are at the top of the Python file."Additional
master(83c22b83)d82c432029to3464590bdf3464590bdfto8db95b3f3f8db95b3f3ftoee202789bcee202789bctoc4cfae2865c4cfae2865to22741ec5fd22741ec5fdtoa1908bb0dcReview Response — Cycle 7 (rebase + WF05 e2e fix)
Addressed the latest CI failure after rebasing onto current
origin/master.✅ Addressed
E2E.Wf05 Db Migrationfailed becauselifecycle-applyprompted interactively in Robot (Apply changes for plan <ID>? [y/N]:)plan lifecycle-apply --yes ${plan_id} --format jsoninrobot/e2e/wf05_db_migration.robotorigin/master; branch is rebased/current (0 behind / 1 ahead)✅ Validation rerun
nox -e lint— passnox -e typecheck— passnox -e unit_tests— passnox -e integration_tests— passnox -e e2e_tests— pass (42 tests)nox -e coverage_report— pass (98%, threshold >=97%)⏸️ Deferred remains unchanged
PR description has been updated to reflect this cycle.
a1908bb0dcto230cc4f730230cc4f730tof7074a2ecaf7074a2ecato63cc79ca4b