Bug: _cleveragents/plan/cancel A2A handler ignores reason parameter — cancellation reason is never passed to PlanLifecycleService.cancel_plan() #2385

Open
opened 2026-04-03 17:27:54 +00:00 by freemo · 1 comment
Owner

Metadata

  • Branch: fix/a2a-plan-cancel-reason-param
  • Commit Message: fix(a2a): forward reason param from _cleveragents/plan/cancel to PlanLifecycleService.cancel_plan()
  • Milestone: v3.4.0
  • Parent Epic: #933

Bug Report

Feature Area: API Endpoints / A2A Protocol — Plan Lifecycle

What was tested

The _cleveragents/plan/cancel A2A extension method handler in A2aLocalFacade (src/cleveragents/a2a/facade.py).

Expected behavior (from spec)

Per docs/specification.md CLI command reference:

agents plan cancel [--reason|-r <REASON>] <PLAN_ID>

The --reason parameter is optional but supported. The spec states "Every CLI command maps to either a standard A2A operation or a _cleveragents/ extension method." The _cleveragents/plan/cancel A2A method should accept and forward a reason parameter.

The underlying PlanLifecycleService.cancel_plan() already accepts a reason parameter:

def cancel_plan(self, plan_id: str, reason: str | None = None) -> Plan:

(See src/cleveragents/application/services/plan_lifecycle_service.py, line 1786)

Actual behavior (from code analysis)

The _handle_plan_cancel handler in src/cleveragents/a2a/facade.py (lines 498–504) extracts plan_id from params but never reads or passes the reason parameter:

def _handle_plan_cancel(self, params: dict[str, Any]) -> dict[str, Any]:
    plan_id = params.get("plan_id", "")
    svc = self._plan_lifecycle_service
    if svc is None or not plan_id:
        return {"plan_id": plan_id, "status": "cancelled", "stub": True}
    svc.cancel_plan(plan_id)  # ← reason is never passed!
    return {"plan_id": plan_id, "status": "cancelled"}

When a client sends {"plan_id": "...", "reason": "user requested"}, the reason is silently dropped. The cancellation is recorded without the reason, losing audit trail information.

Steps to reproduce

from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest

facade = A2aLocalFacade(services={"plan_lifecycle_service": mock_service})
response = facade.dispatch(A2aRequest(
    method="_cleveragents/plan/cancel",
    params={"plan_id": "01HXRCF1...", "reason": "user cancelled"}
))
# The mock_service.cancel_plan() is called WITHOUT the reason argument

Code location

  • src/cleveragents/a2a/facade.py_handle_plan_cancel() method (lines 498–504)

Fix

def _handle_plan_cancel(self, params: dict[str, Any]) -> dict[str, Any]:
    plan_id = params.get("plan_id", "")
    reason: str | None = params.get("reason") or None
    svc = self._plan_lifecycle_service
    if svc is None or not plan_id:
        return {"plan_id": plan_id, "status": "cancelled", "stub": True}
    svc.cancel_plan(plan_id, reason=reason)
    return {"plan_id": plan_id, "status": "cancelled"}

Severity

Medium — Functional correctness issue. Cancellation reasons are lost, breaking audit trail and the spec's parameter contract.

Subtasks

  • Read and confirm the _cleveragents/plan/cancel parameter contract in docs/specification.md
  • Confirm PlanLifecycleService.cancel_plan() signature accepts reason: str | None = None
  • Fix _handle_plan_cancel() in src/cleveragents/a2a/facade.py to extract and forward reason
  • Add/update Behave scenario: _cleveragents/plan/cancel with reason param forwards reason to service
  • Add/update Behave scenario: _cleveragents/plan/cancel without reason param calls service with reason=None
  • Run nox -e typecheck — confirm no type errors
  • Run nox -e unit_tests — confirm all scenarios pass
  • Run nox -e coverage_report — confirm coverage ≥ 97%
  • Run nox (all default sessions) — confirm clean

Definition of Done

  • _handle_plan_cancel() reads reason from params and passes it to PlanLifecycleService.cancel_plan()
  • Behaviour is spec-compliant: optional reason parameter is accepted and forwarded
  • Behave scenarios cover both the reason-present and reason-absent code paths
  • No regressions in existing A2A handler tests
  • All nox stages pass
  • Coverage >= 97%

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-new-issue-creator

## Metadata - **Branch**: `fix/a2a-plan-cancel-reason-param` - **Commit Message**: `fix(a2a): forward reason param from _cleveragents/plan/cancel to PlanLifecycleService.cancel_plan()` - **Milestone**: v3.4.0 - **Parent Epic**: #933 ## Bug Report **Feature Area**: API Endpoints / A2A Protocol — Plan Lifecycle ### What was tested The `_cleveragents/plan/cancel` A2A extension method handler in `A2aLocalFacade` (`src/cleveragents/a2a/facade.py`). ### Expected behavior (from spec) Per `docs/specification.md` CLI command reference: ``` agents plan cancel [--reason|-r <REASON>] <PLAN_ID> ``` The `--reason` parameter is optional but supported. The spec states "Every CLI command maps to either a standard A2A operation or a `_cleveragents/` extension method." The `_cleveragents/plan/cancel` A2A method should accept and forward a `reason` parameter. The underlying `PlanLifecycleService.cancel_plan()` already accepts a `reason` parameter: ```python def cancel_plan(self, plan_id: str, reason: str | None = None) -> Plan: ``` (See `src/cleveragents/application/services/plan_lifecycle_service.py`, line 1786) ### Actual behavior (from code analysis) The `_handle_plan_cancel` handler in `src/cleveragents/a2a/facade.py` (lines 498–504) extracts `plan_id` from params but **never reads or passes the `reason` parameter**: ```python def _handle_plan_cancel(self, params: dict[str, Any]) -> dict[str, Any]: plan_id = params.get("plan_id", "") svc = self._plan_lifecycle_service if svc is None or not plan_id: return {"plan_id": plan_id, "status": "cancelled", "stub": True} svc.cancel_plan(plan_id) # ← reason is never passed! return {"plan_id": plan_id, "status": "cancelled"} ``` When a client sends `{"plan_id": "...", "reason": "user requested"}`, the reason is silently dropped. The cancellation is recorded without the reason, losing audit trail information. ### Steps to reproduce ```python from cleveragents.a2a.facade import A2aLocalFacade from cleveragents.a2a.models import A2aRequest facade = A2aLocalFacade(services={"plan_lifecycle_service": mock_service}) response = facade.dispatch(A2aRequest( method="_cleveragents/plan/cancel", params={"plan_id": "01HXRCF1...", "reason": "user cancelled"} )) # The mock_service.cancel_plan() is called WITHOUT the reason argument ``` ### Code location - `src/cleveragents/a2a/facade.py` — `_handle_plan_cancel()` method (lines 498–504) ### Fix ```python def _handle_plan_cancel(self, params: dict[str, Any]) -> dict[str, Any]: plan_id = params.get("plan_id", "") reason: str | None = params.get("reason") or None svc = self._plan_lifecycle_service if svc is None or not plan_id: return {"plan_id": plan_id, "status": "cancelled", "stub": True} svc.cancel_plan(plan_id, reason=reason) return {"plan_id": plan_id, "status": "cancelled"} ``` ### Severity **Medium** — Functional correctness issue. Cancellation reasons are lost, breaking audit trail and the spec's parameter contract. ## Subtasks - [ ] Read and confirm the `_cleveragents/plan/cancel` parameter contract in `docs/specification.md` - [ ] Confirm `PlanLifecycleService.cancel_plan()` signature accepts `reason: str | None = None` - [ ] Fix `_handle_plan_cancel()` in `src/cleveragents/a2a/facade.py` to extract and forward `reason` - [ ] Add/update Behave scenario: `_cleveragents/plan/cancel` with `reason` param forwards reason to service - [ ] Add/update Behave scenario: `_cleveragents/plan/cancel` without `reason` param calls service with `reason=None` - [ ] Run `nox -e typecheck` — confirm no type errors - [ ] Run `nox -e unit_tests` — confirm all scenarios pass - [ ] Run `nox -e coverage_report` — confirm coverage ≥ 97% - [ ] Run `nox` (all default sessions) — confirm clean ## Definition of Done - [ ] `_handle_plan_cancel()` reads `reason` from `params` and passes it to `PlanLifecycleService.cancel_plan()` - [ ] Behaviour is spec-compliant: optional `reason` parameter is accepted and forwarded - [ ] Behave scenarios cover both the `reason`-present and `reason`-absent code paths - [ ] No regressions in existing A2A handler tests - [ ] All nox stages pass - [ ] Coverage >= 97% --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-new-issue-creator
freemo added this to the v3.4.0 milestone 2026-04-03 17:28:00 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • Priority: Medium — The reason parameter being ignored in plan cancellation means audit trails and user-facing messages lose the cancellation reason.
  • Milestone: v3.4.0
  • MoSCoW: Should Have — The cancellation works functionally, but losing the reason breaks auditability. Important for production use.
  • Parent Epic: #933 (A2A Protocol Compliance)

Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: ca-project-owner

Issue triaged by project owner: - **State**: Verified - **Priority**: Medium — The `reason` parameter being ignored in plan cancellation means audit trails and user-facing messages lose the cancellation reason. - **Milestone**: v3.4.0 - **MoSCoW**: Should Have — The cancellation works functionally, but losing the reason breaks auditability. Important for production use. - **Parent Epic**: #933 (A2A Protocol Compliance) --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: ca-project-owner
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core#2385
No description provided.