Skip to content

move_children_to_parent reports version conflicts as (0, 0) via string-matched store errors #3032

Closed

Description

@malibio

Overview

When NodeService::move_children_to_parent hits an in-transaction version conflict, the error reports expected = 0, actual = 0. The conflict is detected in SqliteStore::move_children_to_parent_in_tx, which returns a plain anyhow error with the message "VERSION_CONFLICT: node '<id>' version mismatch (expected N)". The service then turns that back into an error by string-matching:

if let Some(rest) = msg.strip_prefix("VERSION_CONFLICT: node '") {
    let node_id = rest.split('\'').next().unwrap_or("unknown");
    NodeServiceError::version_conflict(node_id, 0, 0)
}

This has two problems:

  • The contract depends on the message text. Rewording the store's error silently turns a version conflict into a generic QueryFailed. The daemon would then stop mapping it to Aborted, and the frontend would no longer see it as an OCC conflict it can retry or roll back.
  • The versions are wrong. Both are reported as 0. The store knows the expected version, and the conflicting child's actual version can be read inside the same transaction.

The daemon currently maps every VersionConflict to Aborted no matter what versions it carries, so nothing breaks today. The problem is how fragile and misleading the error is.

Proposed Solution

Have the store report the conflict as a typed value instead of a formatted message. For example, return Ok(Err(VersionConflict { node_id, expected, actual })), following the update_node_with_version_check_in_tx pattern (Result<Result<Node, i64>>), or use a typed store error. The service then maps it to NodeServiceError::version_conflict(node_id, expected, actual) with real values, and the string matching is deleted.

Check for other store methods that signal OCC failures by message prefix, and convert them the same way if any exist.

Acceptance Criteria

  • move_children_to_parent_in_tx reports a version conflict as a typed value carrying the node id, the expected version and the actual version
  • The service's strip_prefix("VERSION_CONFLICT: …") parsing is removed
  • NodeServiceError::VersionConflict from move_children_to_parent carries the real expected and actual versions (tests assert them)
  • No other store path signals OCC conflicts through message text

Reference Files

  • packages/core/src/db/sqlite_store/nodes.rs: move_children_to_parent_in_tx
  • packages/core/src/services/node_service/hierarchy.rs: move_children_to_parent (the error mapping)
  • packages/core/src/db/sqlite_store/nodes.rs: update_node_with_version_check_in_tx (the existing typed-conflict pattern)

Related

Activity

  1. malibio commented on Sep 25, 2026

    @malibio
    CollaboratorAuthor

    Frontend: 5293 passed (265 files)

  2. self-assigned this
    on Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions