BUG-HUNT: data-integrity — SubplanMergeService._sequential_apply() passes stale base to every merge iteration #7721

Open
opened 2026-04-12 03:20:48 +00:00 by HAL9000 · 3 comments
Owner

Bug Report: data-integrity — SubplanMergeService._sequential_apply() passes stale base to every merge iteration

Severity Assessment

  • Impact: The sequential merge produces silently corrupted output when 3 or more subplans modify the same file. The ancestor used in each three-way merge is always the original base_content instead of the previously merged content, causing intermediate changes to be treated as if they never happened. This violates the specification intent of "each becomes the new base."
  • Likelihood: Triggered every time SEQUENTIAL_APPLY is used on a file touched by 3+ subplans — a normal production scenario with parallel sub-teams.
  • Priority: High

Location

  • File: src/cleveragents/application/services/subplan_merge_service.py
  • Function/Class: SubplanMergeService._sequential_apply
  • Lines: 228–245

Description

The docstring says "Apply changes sequentially (each becomes the new base)." However, the loop calls self._seq_merge.merge(base_content, current, content) where the first argument (base_content) is the original content and never changes. In a proper sequential merge, after the first iteration the result of merging should become the new ancestor. Instead, the original base is reused every time, so intermediate changes from subplan N are invisible to the merge() call for subplan N+1.

Evidence

def _sequential_apply(
    self,
    path: str,
    base_content: str,
    contents: list[str],
    subplan_ids: list[str],
) -> FileMergeOutcome:
    current = base_content
    for content in contents:
        # BUG: base_content is always the original — never advances
        result: MergeResult = self._seq_merge.merge(base_content, current, content)
        current = result.content

For a file with original content A and three subplans producing B, C, D:

  • Iteration 1: merge(A, A, B) -> result = B (correct)
  • Iteration 2: merge(A, B, C) -> ancestor=A (stale, should be B)
  • Iteration 3: merge(A, C, D) -> ancestor=A (stale, should be C)

Expected Behavior

The merge ancestor should advance with each iteration so subplan N+1 sees subplan N result as its base:

prev = current
result = self._seq_merge.merge(prev, prev, content)
current = result.content

Actual Behavior

The original base content is reused as the ancestor in every merge call. Subplan N+1 merges against the original base, causing earlier changes to appear as conflicts or be silently dropped.

Suggested Fix

def _sequential_apply(self, path, base_content, contents, subplan_ids):
    current = base_content
    for content in contents:
        prev = current
        result: MergeResult = self._seq_merge.merge(prev, prev, content)
        current = result.content
    return FileMergeOutcome(path=path, content=current, has_conflict=False, source_subplan_ids=subplan_ids)

Category

data-integrity

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: data-integrity — `SubplanMergeService._sequential_apply()` passes stale base to every merge iteration ### Severity Assessment - **Impact**: The sequential merge produces silently corrupted output when 3 or more subplans modify the same file. The ancestor used in each three-way merge is always the original `base_content` instead of the previously merged content, causing intermediate changes to be treated as if they never happened. This violates the specification intent of "each becomes the new base." - **Likelihood**: Triggered every time `SEQUENTIAL_APPLY` is used on a file touched by 3+ subplans — a normal production scenario with parallel sub-teams. - **Priority**: High ### Location - **File**: `src/cleveragents/application/services/subplan_merge_service.py` - **Function/Class**: `SubplanMergeService._sequential_apply` - **Lines**: 228–245 ### Description The docstring says "Apply changes sequentially (each becomes the new base)." However, the loop calls `self._seq_merge.merge(base_content, current, content)` where the first argument (`base_content`) is the **original** content and never changes. In a proper sequential merge, after the first iteration the result of merging should become the new ancestor. Instead, the original base is reused every time, so intermediate changes from subplan N are invisible to the `merge()` call for subplan N+1. ### Evidence ```python def _sequential_apply( self, path: str, base_content: str, contents: list[str], subplan_ids: list[str], ) -> FileMergeOutcome: current = base_content for content in contents: # BUG: base_content is always the original — never advances result: MergeResult = self._seq_merge.merge(base_content, current, content) current = result.content ``` For a file with original content A and three subplans producing B, C, D: - Iteration 1: merge(A, A, B) -> result = B (correct) - Iteration 2: merge(A, B, C) -> ancestor=A (stale, should be B) - Iteration 3: merge(A, C, D) -> ancestor=A (stale, should be C) ### Expected Behavior The merge ancestor should advance with each iteration so subplan N+1 sees subplan N result as its base: ```python prev = current result = self._seq_merge.merge(prev, prev, content) current = result.content ``` ### Actual Behavior The original base content is reused as the ancestor in every merge call. Subplan N+1 merges against the original base, causing earlier changes to appear as conflicts or be silently dropped. ### Suggested Fix ```python def _sequential_apply(self, path, base_content, contents, subplan_ids): current = base_content for content in contents: prev = current result: MergeResult = self._seq_merge.merge(prev, prev, content) current = result.content return FileMergeOutcome(path=path, content=current, has_conflict=False, source_subplan_ids=subplan_ids) ``` ### Category data-integrity ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-12 03:41:19 +00:00
Author
Owner

Verified — Data integrity bug: SubplanMergeService passes stale base to every merge iteration — incorrect merge results. MoSCoW: Must-have. Priority: High.


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

✅ **Verified** — Data integrity bug: SubplanMergeService passes stale base to every merge iteration — incorrect merge results. MoSCoW: Must-have. Priority: High. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Data integrity bug: SubplanMergeService passes stale base to every merge iteration — incorrect merge results. MoSCoW: Must-have. Priority: High.


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

✅ **Verified** — Data integrity bug: SubplanMergeService passes stale base to every merge iteration — incorrect merge results. MoSCoW: Must-have. Priority: High. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Data integrity bug: SubplanMergeService passes stale base to every merge iteration — incorrect merge results. MoSCoW: Must-have. Priority: High.


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

✅ **Verified** — Data integrity bug: SubplanMergeService passes stale base to every merge iteration — incorrect merge results. MoSCoW: Must-have. Priority: High. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
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#7721
No description provided.