Originally created by: adityaharishch
Summary
Follow-up audit after the recent pm_worklog stuck-hour fix (#471). Went through src/ looking for the same failure class (state that can wedge silently) plus general error-handling gaps: bare unwrap(), swallowed errors, and missing .context() on the DB/IO path.
etl/runner.rs: added .context() to every DB/IO call in run_etl — previously the daemon's most crash-sensitive loop propagated bare errors with no indication of which step failed (cursor read, frame batch read, gap insert, block close, cursor advance, etc).
main.rs: the two etl_tick_span mutex locks used a plain .lock().unwrap(); made them poison-tolerant (unwrap_or_else(|e| e.into_inner())), matching the pattern already used elsewhere in this codebase for the same hazard — a panic anywhere else while holding the lock would otherwise permanently kill future tick spans.
session_categorizer: partial_cmp(...).unwrap() in the score-winner comparison could panic on a NaN score; switched to total_cmp which is total-order and panic-free.
pm_worklog/generate.rs: the recovery writes that run after a worklog action fails (mark_error, revert_create, revert_post) were silently swallowing their own failures (let _ = ...). The primary error was already logged, but if the recovery write itself failed, that was invisible. Now logged via tracing::warn!.
Also audited for other "stuck state after crash" bugs analogous to the pm_worklog one — checked etl_runs, day_task_worklogs, and other status-column tables; all either already have startup/tick-time recovery or are dev-only/dead-code paths not reachable in normal daemon operation, so no further table needed a fix.
Test plan
- [x]
cargo build
- [x]
cargo fmt --check
- [x]
cargo clippy --all-targets -- -D warnings
- [x]
cargo test --lib (669 passed)
- [x] ETL integration test targets (
etl_basic, etl_gaps, etl_session_close, etl_session_text, etl_ui_events, etl_coding_agent_skip)
- [x] pre-push hook suite (fmt, clippy, ui build/tests, security audit, cargo test) — all green
Originally posted by: coderabbitai[bot]
✨ Finishing Touches
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests - [ ] Commit unit tests in branch `fix/error-handling-hardening`Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
❤️ Share
- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)Comment
@coderabbitai helpto get the list of available commands.Originally posted by: Akarsh-Hegde
Overview
Small, well-scoped follow-up to [#471] auditing
src/for the same failure class ("state that can silently wedge") plus general error-handling gaps. Four files touched, +62/-26:src/etl/runner.rs: adds.context("…")to every?on a DB/IO call inrun_etl(cursor read, frame batch reads, gap inserts, block closes, cursor advance, run completion).src/main.rs: makes the twoetl_tick_span.lock()sites poison-tolerant (unwrap_or_else(|e| e.into_inner())).src/intelligence/session_categorizer/mod.rs: replacespartial_cmp(...).unwrap()withtotal_cmpinScores::winner()to remove a NaN-panic path.src/pm_worklog/generate.rs: turns threelet _ = ...swallowed recovery writes (mark_error,revert_create,revert_post) into logged failures viatracing::warn!.No ETL logic changes — only added
.context()around existing?calls, so the documented ETL invariants (gap classification,duration_sexcluding gap time, Option C/D, cross-run vs intra-batch gap handling) are untouched. Confirmed no test intests/etl_*.rsasserts on exact error-message text, so wrapping errors in context strings can't break the suite.Strengths
unwrap_or_else(|e| e.into_inner())is the established poison-tolerant pattern already used insrc/embedder/mod.rsand three call sites each insrc/llm/detect.rs/rate_limit.rs/resolver.rs. This PR correctly extends it to the twoetl_tick_spansites inmain.rs, and both writer sites were caught (no.lock()on that mutex was missed).total_cmpis a real behavior improvement, not just cosmetic.Scoresis a fixed[f32; 10]array populated only viaadd(), so the.expect("scores array is fixed-size and non-empty")replacing.unwrap()is accurate and safe. For all-finite scores,total_cmpandpartial_cmporder identically, so there's no regression for the normal path — this is the one actual behavioral change in the diff (everything else is additive: context strings and logging are no-ops for the happy path).pm_worklog/generate.rsfixes are consistent with existing style in the same function — the file already has an equivalentif let Err(e) = ...pattern a few lines below the last change (linking the day-task card, "best-effort" comment), so this isn't introducing a new idiom, just applying the existing one to previously-silent spots.warn!calls use structured fields (error = %e, task_id, task_key), matching the project's tracing conventions..context()messages name the specific failing step ("failed to read etl_cursor", "failed to insert cross-run gap row", etc.), which is exactly the debuggability this PR is going for.Issues / Suggestions
if let Err(e) = resultbranch (runner.rs), both recovery writes now use.context(...)?:rust complete_etl_run(meridian, run_id, sessions_closed, Some(&e.to_string())) .await .context("failed to mark etl_runs row failed")?; update_cursor(meridian, last_processed_id, run_id) .await .context("failed to advance etl_cursor after failed run")?; return Err(e);If either of these recovery writes itself fails, the function returns that error instead of the original ETL failure
e— the same "recovery write masks/loses the real error" hazard this PR fixes on thepm_worklogside by convertinglet _ =into log-and-continue. Sinceeis already captured by thewarn!immediately above, and a stuckrunningetl_runsrow is reclaimed bycleanup_incomplete_runson startup, nothing is actually lost today — but for consistency with the PR's own theme, consider logging-and-continuing here too (soreturn Err(e)always returns the real cause), rather than propagating whichever failure happened last.total_cmpis the one behavioral change in the diff; a one-line unit test insession_categorizerassertingwinner()doesn't panic on a NaN-containingScoreswould pin down the fix (currently unverified beyond "doesn't panic in practice").git grepon the PR branch still shows ~20 remaininglet _ = ...awaitswallowed-error sites elsewhere insrc/(intelligence/providers/{jira,github,linear,trello}/mod.rs,main.rs,notices.rs) and roughly 459 bare.unwrap()calls overall. A couple of the swallowed ones —stamp_sync_error/clear_sync_errorin the PM-provider sync paths — are the same failure class as the worklog fix here (a failed "record the sync error" write is itself invisible) and look like a natural next target for a follow-up PR.Risks
total_cmp) is a strict improvement over a panic and is provably equivalent for finite inputs.Test coverage
cargo build,cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo test --lib(669 passed), and the named ETL integration targets (etl_basic,etl_gaps,etl_session_close,etl_session_text,etl_ui_events,etl_coding_agent_skip) plus the full pre-push suite — consistent with the diff being non-invasive to ETL semantics.total_cmpNaN-panic fix or the newwarn!paths ingenerate.rs; given the small blast radius I'd call this acceptable for a hardening PR of this size, but see suggestion [#2] above.Overall: a clean, low-risk, well-motivated hardening pass. Non-blocking suggestions above; happy to see this merged as-is if the team doesn't want to scope-creep it further.
Related
Tickets:
#2Tickets:
#471Originally posted by: adityaharishch
Pushed a follow-up commit addressing both non-blocking suggestions:
etl/runner.rs: the failure-recovery writes (complete_etl_run,update_cursor) now log-and-continue on error instead of propagating via?, so a recovery-write failure can no longer mask/replace the original ETL errorethat's returned.winner_does_not_panic_on_nan_scoreinsession_categorizer, pinning thetotal_cmpNaN-safety fix (a NaN score wins outright under total order rather than panicking likepartial_cmpwould).Ticket changed by: adityaharishch