fix(core): stop reconciliation thread + mark batch FAILED when execution raises (#763)#847
Conversation
…ion raises (#763) `_execute_serial`/`_execute_parallel` called `reconcile_stop.set()` only on the success path, so an unexpected worker/setup error (e.g. create_execution_context failing) leaked the polling reconciliation daemon and left the batch row RUNNING forever in the long-lived server. Move the stop signal into `finally` (guaranteed teardown) and add an `except` that marks the batch FAILED, emits the terminal BATCH_FAILED event, then re-raises to preserve callers' existing propagation/fallback behavior. Adds a regression test asserting the batch goes FAILED and the reconcile event is set when a worker raises.
|
Claude finished @frankbria's task in 6m 45s —— View job Bug-hunting review of #763
Scope: concrete defects only (logic errors, error-path data loss, regressions). Style/architecture/test-coverage left to the general review. Summary
Both have inline comments with drop-in suggestion blocks that guard the FAILED transition on the persisted status (only flip to FAILED when no terminal status was saved yet — the actual #763 "stuck RUNNING" case). What's correct here: moving |
Demo verification (outcome evidence)Non-web correctness fix — evidence comes from driving the real batch failure path via
|
ReviewSolid fix for the core leak (reconcile thread never stopped + batch stuck One correctness concern and a couple of scope/coverage notes: 1. The In both The added test only forces the failure inside 2. Same 'stuck forever' bug class is still open in the resume/retry paths
3. Test coverage is serial-only The fix is duplicated by hand across Minor: If No security concerns; nothing here touches user input or auth boundaries. |
… review) The over-broad `except Exception` from the previous commit wrapped the whole finalization tail, so an error raised *after* a terminal COMPLETED/PARTIAL was persisted (e.g. `emit_for_workspace` hitting `database is locked`) would overwrite that correct record with FAILED and emit a spurious BATCH_FAILED. Guard the FAILED override with a `batch_finalized` flag set right after the terminal `_save_batch`; the except now only marks FAILED when no terminal status was persisted yet. The reconcile-thread stop stays unconditional in `finally`. Adds a regression test for the finalization-tail case.
|
Claude finished @frankbria's task in 5m 53s —— View job Bug-hunting re-review of #763 (f93d1b4)Triggered by the new commit scoping the FAILED override. Scope: concrete defects only, no repeat of already-posted/fixed findings.
Verdict: ✅ GLM review: no new defects found in f93d1b4. The follow-up commit correctly resolves the two
What I confirmed against the real code (not just the diff):
Not re-reported (already raised or explicitly acknowledged): the resume/retry paths ( |
Review (follow-up)Looked at the latest commit ( Point 1 from my prior review is fixed. The The new Still open from my prior review (not new, not blocking this PR, just flagging they weren't addressed here):
No security concerns. Nothing here touches auth or user input boundaries. |
Closes #763
Problem
_execute_serial/_execute_parallelcalledreconcile_stop.set()only on the success path. An unexpected worker/setup error (e.g.create_execution_contextfailing) leaked the polling reconciliation daemon thread and left the batch rowRUNNINGforever in the long-lived server.Fix
finally— guaranteed teardown whether execution succeeds or raises.except Exceptionblock that marks the batchFAILED, emits the terminalBATCH_FAILEDevent, then re-raises so callers keep their existing propagation/fallback behavior (the auto-strategy path still falls back to serial on a non-cycle error).Applied symmetrically to both
_execute_serialand_execute_parallel.Acceptance criteria
reconcile_stop.set()moved intofinallyFAILEDTests
Added
test_unexpected_exception_marks_failed_and_stops_reconcile: forces a worker to raise, asserts the batch endsFAILED(not stuckRUNNING) and the reconcile event is set (no daemon leak).uv run pytest tests/core/test_conductor.py→ 65 passed (1 unrelated pre-existingdatabase is lockedcontention flake under the full 26-min serial run; passes in isolation). ruff + mypy clean.Known limitations
On the auto-strategy fallback path, marking
FAILEDbefore re-raising is transient — the fallback serial run immediately overwrites it with the true terminal status. Harmless; theFAILEDmarking only sticks when the exception is genuinely unhandled.