Skip to content

fix: transaction safety, resource cleanup, and truncation signaling (#258, #259, #260, #261)#279

Open
azizur100389 wants to merge 1 commit intotirth8205:mainfrom
azizur100389:fix/reliability-hardening
Open

fix: transaction safety, resource cleanup, and truncation signaling (#258, #259, #260, #261)#279
azizur100389 wants to merge 1 commit intotirth8205:mainfrom
azizur100389:fix/reliability-hardening

Conversation

@azizur100389
Copy link
Copy Markdown
Contributor

Summary

Four reliability hardening fixes that improve crash safety, resource cleanup, and API transparency across the graph pipeline. 8 files changed, +206 / -25, 6 new regression tests.

Closes #258, closes #259, closes #260, closes #261.


Fix 1 — Wrap incremental_trace_flows DELETEs in a transaction (#258)

File: code_review_graph/flows.py:499-502

The DELETE loop deleted flow_memberships and flows rows one at a time without an explicit transaction. A crash mid-loop could leave orphaned flow_memberships rows pointing at deleted flows.

Fix: Wrap in BEGIN IMMEDIATE / COMMIT / ROLLBACK, matching the pattern already used by store_flows at line 406. Includes an in_transaction guard to flush any implicit transaction first.

Test: test_incremental_trace_flows_delete_is_atomic — verifies zero orphaned memberships after an incremental re-trace.

Fix 2 — Atomic FTS index rebuild (#259)

File: code_review_graph/search.py:39-54

rebuild_fts_index performed DROP, CREATE, and INSERT across three separate statements with commits between them. A crash after DROP but before CREATE would leave the DB without an FTS table entirely.

Fix: Wrap the entire DROP + CREATE + INSERT sequence in a single BEGIN IMMEDIATE transaction so the operation is all-or-nothing.

Test: test_fts_rebuild_is_atomic — verifies double-rebuild leaves the table intact and queryable.

Fix 3 — EmbeddingStore context manager support (#260)

File: code_review_graph/embeddings.py:391+

EmbeddingStore opens a SQLite connection in __init__ but did not implement __enter__ / __exit__. If an exception occurred during usage before close() was called, the connection leaked.

Fix: Add __enter__ and __exit__ methods following the same pattern as GraphStore (graph.py:154-157).

Tests: test_supports_context_manager, test_context_manager_closes_on_exception.

Fix 4 — Truncation signal for find_dependents (#261)

File: code_review_graph/incremental.py:434-465

When find_dependents hit _MAX_DEPENDENT_FILES (500), it truncated the result and logged a warning, but the caller received a plain list with no indication that it was incomplete.

Fix: Introduce DependentList — a transparent list subclass with a .truncated boolean attribute. Existing callers that iterate, len(), or slice continue to work unchanged; only callers that specifically check .truncated benefit from the signal.

Tests: test_truncated_flag_set_when_capped (600 deps → truncated=True), test_truncated_flag_false_when_not_capped (small chain → truncated=False).


Files changed (8 files, +206 / -25)

File Change
code_review_graph/flows.py Transaction wrap around DELETE loop
code_review_graph/search.py Atomic FTS rebuild
code_review_graph/embeddings.py __enter__/__exit__ on EmbeddingStore
code_review_graph/incremental.py DependentList with .truncated flag
tests/test_flows.py Orphan membership check
tests/test_search.py Double-rebuild atomicity test
tests/test_embeddings.py Context manager tests
tests/test_incremental.py Truncation flag tests

Test results

Stage Result
New targeted tests 6/6 passed
All affected test files 137 passed, 3 pre-existing failures (covered by parallel PRs #274/#276)
Full suite 784 passed, 8 pre-existing Windows failures (all covered by parallel PRs)
ruff check on all 8 changed files clean
mypy on all 4 changed source files clean

Zero regressions. All fixes follow established patterns already used elsewhere in the codebase.

…irth8205#258, tirth8205#259, tirth8205#260, tirth8205#261)

Four reliability hardening fixes that improve crash safety, resource
cleanup, and API transparency across the graph pipeline.

Fix 1: wrap incremental_trace_flows DELETEs in a transaction (tirth8205#258)
-------------------------------------------------------------------
File: code_review_graph/flows.py:499-502

The DELETE loop in incremental_trace_flows deleted flow_memberships and
flows rows one at a time without an explicit transaction.  A crash
mid-loop could leave orphaned flow_memberships rows pointing at deleted
flows.

Fix: wrap the DELETE loop in BEGIN IMMEDIATE / COMMIT / ROLLBACK,
matching the pattern already used by store_flows at line 406.  Includes
an in_transaction guard to flush any implicit transaction first.

Test: test_incremental_trace_flows_delete_is_atomic — verifies no
orphaned memberships exist after an incremental re-trace.

Fix 2: atomic FTS index rebuild (tirth8205#259)
--------------------------------------
File: code_review_graph/search.py:39-54

rebuild_fts_index performed DROP, CREATE, and INSERT across three
separate statements with commits between them.  A crash after DROP but
before CREATE would leave the DB without an FTS table entirely.

Fix: wrap the entire DROP + CREATE + INSERT sequence in a single
BEGIN IMMEDIATE transaction so the operation is all-or-nothing.

Test: test_fts_rebuild_is_atomic — verifies double-rebuild leaves the
table intact and queryable.

Fix 3: EmbeddingStore context manager support (tirth8205#260)
----------------------------------------------------
File: code_review_graph/embeddings.py:391+

EmbeddingStore opens a SQLite connection in __init__ but did not
implement __enter__/__exit__.  If an exception occurred during usage
before close() was called, the connection leaked.

Fix: add __enter__ and __exit__ methods following the same pattern as
GraphStore (graph.py:154-157).

Test: test_supports_context_manager, test_context_manager_closes_on_exception.

Fix 4: truncation signal for find_dependents (tirth8205#261)
----------------------------------------------------
File: code_review_graph/incremental.py:434-465

When find_dependents hit _MAX_DEPENDENT_FILES (500), it truncated the
result and logged a warning, but the caller received a plain list with
no indication that it was incomplete.  Callers had to guess whether
they got the full dependent set or a truncated one.

Fix: introduce DependentList (a transparent list subclass with a
.truncated boolean attribute).  Existing callers that iterate, len(),
or slice continue to work unchanged; only callers that specifically
check .truncated benefit from the signal.

Tests: test_truncated_flag_set_when_capped (600 deps, expects True),
test_truncated_flag_false_when_not_capped (small chain, expects False).

Files changed
-------------
- code_review_graph/flows.py — transaction wrap around DELETE loop
- code_review_graph/search.py — atomic FTS rebuild
- code_review_graph/embeddings.py — __enter__/__exit__ on EmbeddingStore
- code_review_graph/incremental.py — DependentList with .truncated flag
- tests/test_flows.py — orphan membership check
- tests/test_search.py — double-rebuild atomicity test
- tests/test_embeddings.py — context manager tests
- tests/test_incremental.py — truncation flag tests

Test results
------------
Stage 1 (new targeted tests): 6/6 passed.
Stage 2 (all affected test files): 137 passed, 3 pre-existing failures
  (find_repo_root flakiness + UTF-8 gitignore — covered by PRs tirth8205#274/tirth8205#242).
Stage 3 (full suite): 784 passed, 8 pre-existing Windows failures
  (all covered by parallel PRs).
Stage 4 (ruff check on all 8 changed files): clean.
Stage 5 (mypy on all 4 changed source files): clean.

Zero regressions.
@azizur100389 azizur100389 force-pushed the fix/reliability-hardening branch from 474e507 to f55681c Compare April 14, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant