Skip to content

sqlite3.OperationalError: cannot start a transaction within a transaction on update when git diff includes deleted files #135

@Syntek-Studio

Description

@Syntek-Studio

Environment

  • Python: 3.14.3
  • SQLite: (system default)
  • code-review-graph: (installed version)
  • OS: Linux

Steps to reproduce

  1. Have a working tree where git diff --name-only HEAD~1 returns a mix of deleted files and modified/added files (e.g. a branch with file deletions plus code changes).
  2. Run code-review-graph update.

Error

sqlite3.OperationalError: cannot start a transaction within a transaction

Full traceback:

File "code_review_graph/tools/build.py", line 311, in build_or_update_graph
    result = incremental_update(root, store, base=base)
File "code_review_graph/incremental.py", line 515, in incremental_update
    store.store_file_nodes_edges(str(repo_root / rel_path), nodes, edges, fhash)
File "code_review_graph/graph.py", line 239, in store_file_nodes_edges
    self._conn.execute("BEGIN IMMEDIATE")
sqlite3.OperationalError: cannot start a transaction within a transaction

Root cause

GraphStore.__init__ opens the connection with the default isolation_level="". Python's sqlite3 module automatically issues an implicit BEGIN DEFERRED before the first DML statement in that mode.

In incremental_update (and full_build), when deleted files are detected (line 471 / line 356), store.remove_file_data() issues DELETE statements that silently open an implicit DEFERRED transaction. This transaction is never committed before store_file_nodes_edges is called for the next file, which then tries BEGIN IMMEDIATE — SQLite rejects nesting an explicit IMMEDIATE inside an already-open DEFERRED transaction.

Note: the same issue exists in full_build (lines 355–356 call remove_file_data on stale files before the parse loop).

Suggested fix

In graph.py, store_file_nodes_edges, flush any pending implicit transaction before issuing BEGIN IMMEDIATE:

def store_file_nodes_edges(self, file_path, nodes, edges, fhash=""):
    """Atomically replace all data for a file."""
    # Flush any implicit transaction started by a prior DML call (e.g.
    # remove_file_data DELETE statements). Python's sqlite3 auto-begins a
    # DEFERRED transaction on the first DML; BEGIN IMMEDIATE inside an active
    # transaction raises OperationalError on Python 3.12+.
    if self._conn.in_transaction:
        self._conn.commit()
    self._conn.execute("BEGIN IMMEDIATE")
    ...

Connection.in_transaction is available since Python 3.2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions