UAT: PlanLifecycleService.create_action() silently overwrites existing action with same name in in-memory mode — no duplicate check in non-persisted path #5841

Open
opened 2026-04-09 10:34:02 +00:00 by HAL9000 · 1 comment
Owner

Bug Report

Feature Area

Action management — PlanLifecycleService.create_action()

What Was Tested

Runtime testing of PlanLifecycleService.create_action() in in-memory mode (no UnitOfWork provided).

Expected Behavior (from spec)

Creating an action with a name that already exists should raise an error (e.g., DuplicateActionError or ValidationError). The spec treats the namespaced name as the unique identifier for actions — there should be no silent overwrite.

Actual Behavior (from test)

In in-memory mode (no UnitOfWork), create_action() silently overwrites an existing action with the same namespaced name. The second call succeeds and the first action's data is lost.

Steps to Reproduce

from unittest.mock import MagicMock
from cleveragents.application.services.plan_lifecycle_service import PlanLifecycleService

settings = MagicMock()
settings.async_enabled = False
settings.default_automation_profile = "manual"
svc = PlanLifecycleService(settings=settings)  # No UnitOfWork = in-memory mode

# Create first action
action1 = svc.create_action(
    name="local/my-action",
    description="First version",
    definition_of_done="Tests pass",
    strategy_actor="openai/gpt-4",
    execution_actor="openai/gpt-4",
)

# Create second action with same name — should fail but silently overwrites
action2 = svc.create_action(
    name="local/my-action",
    description="Second version - overwrites first!",
    definition_of_done="Tests pass",
    strategy_actor="openai/gpt-4",
    execution_actor="openai/gpt-4",
)

retrieved = svc.get_action("local/my-action")
print(retrieved.description)  # Prints "Second version - overwrites first!"
# First action is silently lost

Code Location

src/cleveragents/application/services/plan_lifecycle_service.py, create_action() method:

# Persist or store in-memory
if self._persisted and self.unit_of_work is not None:
    with self.unit_of_work.transaction() as ctx:
        self._persist_action_create(action, ctx)  # Raises DuplicateActionError if duplicate
self._actions[action_name_str] = action  # ← Always overwrites, no duplicate check

In persisted mode, DuplicateActionError is raised by the repository before the in-memory dict is updated, so duplicates are correctly rejected. But in in-memory mode (no UnitOfWork), there is no duplicate check and the dict is silently overwritten.

Impact

  • In-memory mode: Silent data loss — existing action overwritten without error
  • Persisted mode: Correctly raises DuplicateActionError from the repository

Fix Required

Add a duplicate check in the in-memory path:

# Check for duplicate in in-memory store
if action_name_str in self._actions:
    from cleveragents.infrastructure.database.repositories import DuplicateActionError
    raise DuplicateActionError(action_name_str)

# Persist or store in-memory
if self._persisted and self.unit_of_work is not None:
    with self.unit_of_work.transaction() as ctx:
        self._persist_action_create(action, ctx)
self._actions[action_name_str] = action

Or alternatively, the CLI should surface the DuplicateActionError with a user-friendly message.


Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: uat-tester

## Bug Report ### Feature Area Action management — `PlanLifecycleService.create_action()` ### What Was Tested Runtime testing of `PlanLifecycleService.create_action()` in in-memory mode (no `UnitOfWork` provided). ### Expected Behavior (from spec) Creating an action with a name that already exists should raise an error (e.g., `DuplicateActionError` or `ValidationError`). The spec treats the namespaced name as the unique identifier for actions — there should be no silent overwrite. ### Actual Behavior (from test) In in-memory mode (no `UnitOfWork`), `create_action()` silently overwrites an existing action with the same namespaced name. The second call succeeds and the first action's data is lost. ### Steps to Reproduce ```python from unittest.mock import MagicMock from cleveragents.application.services.plan_lifecycle_service import PlanLifecycleService settings = MagicMock() settings.async_enabled = False settings.default_automation_profile = "manual" svc = PlanLifecycleService(settings=settings) # No UnitOfWork = in-memory mode # Create first action action1 = svc.create_action( name="local/my-action", description="First version", definition_of_done="Tests pass", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) # Create second action with same name — should fail but silently overwrites action2 = svc.create_action( name="local/my-action", description="Second version - overwrites first!", definition_of_done="Tests pass", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) retrieved = svc.get_action("local/my-action") print(retrieved.description) # Prints "Second version - overwrites first!" # First action is silently lost ``` ### Code Location `src/cleveragents/application/services/plan_lifecycle_service.py`, `create_action()` method: ```python # Persist or store in-memory if self._persisted and self.unit_of_work is not None: with self.unit_of_work.transaction() as ctx: self._persist_action_create(action, ctx) # Raises DuplicateActionError if duplicate self._actions[action_name_str] = action # ← Always overwrites, no duplicate check ``` In persisted mode, `DuplicateActionError` is raised by the repository before the in-memory dict is updated, so duplicates are correctly rejected. But in in-memory mode (no `UnitOfWork`), there is no duplicate check and the dict is silently overwritten. ### Impact - **In-memory mode**: Silent data loss — existing action overwritten without error - **Persisted mode**: Correctly raises `DuplicateActionError` from the repository ### Fix Required Add a duplicate check in the in-memory path: ```python # Check for duplicate in in-memory store if action_name_str in self._actions: from cleveragents.infrastructure.database.repositories import DuplicateActionError raise DuplicateActionError(action_name_str) # Persist or store in-memory if self._persisted and self.unit_of_work is not None: with self.unit_of_work.transaction() as ctx: self._persist_action_create(action, ctx) self._actions[action_name_str] = action ``` Or alternatively, the CLI should surface the `DuplicateActionError` with a user-friendly message. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
HAL9000 added this to the v3.5.0 milestone 2026-04-09 13:21:25 +00:00
Author
Owner

Milestone compliance fix applied:

  • Assigned to milestone: v3.5.0 (Autonomy Hardening)
  • Reason: Issue is State/Verified but had no milestone. PlanLifecycleService action management belongs to v3.5.0 scope.

Automated by CleverAgents Bot
Supervisor: Backlog Grooming | Agent: backlog-groomer

Milestone compliance fix applied: - Assigned to milestone: **v3.5.0** (Autonomy Hardening) - Reason: Issue is `State/Verified` but had no milestone. `PlanLifecycleService` action management belongs to v3.5.0 scope. --- **Automated by CleverAgents Bot** Supervisor: Backlog Grooming | Agent: backlog-groomer
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.

Dependencies

No dependencies set.

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