Problem
Latent thread-safety concern surfaced by #414. Low severity (abort path, process exiting), tracked separately so it isn't lost.
Detail
Before #414, on Ctrl+C the drain threads were already joined (dead) when the `_run_cocoindex_update_impl` `finally` ran its terminal `on_progress(...) → renderer.apply()`.
After #414 the abort path deliberately re-raises without joining the drain threads (joining is what hung). So a still-live drain thread's `ProgressRelay._route_line → apply` can run concurrently with the main thread's `finally → apply`. `rich.Progress` is not documented thread-safe.
Mitigations already in place
- Bounded: abort path only, process exits via `os._exit` immediately after.
- `ProgressRelay._route_line` guards its `apply` with `except Exception` (`src/java_codebase_rag/progress.py`), so the drain side can't crash.
Fix idea (if pursued)
Give `IndexProgressRenderer.apply` an internal lock so concurrent callers serialize. Low priority given the abort context.
Other minor items noted in the #414 review (bundled here to avoid spam)
- Footer/exit-code mismatch: `cli._run_pipeline` renders a footer from `code = 2` in its `finally`, but the process actually exits `130` via the new handler. Cosmetic, cli indexing path only.
- Exit code 130 (indexing) vs 2 (interactive `install` prompt) differ by phase — pre-existing; document if exit-code SLAs matter to operators/scripts.
- Test coverage gaps: `_abort_child` non-`KeyboardInterrupt` abort exception; `_popen_capturing_stderr` normal capture under `verbose=False` (`_LineFilter`) and `on_progress` (`ProgressRelay`) branches.
Problem
Latent thread-safety concern surfaced by #414. Low severity (abort path, process exiting), tracked separately so it isn't lost.
Detail
Before #414, on Ctrl+C the drain threads were already joined (dead) when the `_run_cocoindex_update_impl` `finally` ran its terminal `on_progress(...) → renderer.apply()`.
After #414 the abort path deliberately re-raises without joining the drain threads (joining is what hung). So a still-live drain thread's `ProgressRelay._route_line → apply` can run concurrently with the main thread's `finally → apply`. `rich.Progress` is not documented thread-safe.
Mitigations already in place
Fix idea (if pursued)
Give `IndexProgressRenderer.apply` an internal lock so concurrent callers serialize. Low priority given the abort context.
Other minor items noted in the #414 review (bundled here to avoid spam)