fix(flows): store resilience — skip corrupt rows, transactional step upserts, once-per-process schema init - #5294
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 49 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
Comment |
4618fdf to
26efad3
Compare
|
Review follow-up: fixed a regression this PR introduced. Gating the schema DDL behind a per-path "already initialized" set cost the store its self-healing. Previously the DDL ran on every Reproduced directly: after deleting Fix: a cache hit is now confirmed against the file on disk via one indexed 564 flows tests pass, |
26efad3 to
c001051
Compare
…upserts, once-per-process schema init R-M4: list_flows / list_enabled_flows used to fail their entire query on the first row whose graph_json couldn't parse/migrate (e.g. after a downgrade from a build that persisted a newer schema_version) — bricking flows_list, all app_event trigger dispatch (bus::handle_app_event), and the boot schedule-trigger reconcile sweep. They now skip and log the bad row (id + error only, never graph_json) and return a skipped count, which flows_list/reconcile_schedule_triggers_on_boot/handle_app_event surface via warn logs (and flows_list via RpcOutcome.logs) instead of staying silent about a shorter-than-expected list. R-m1: upsert_flow_run_step's read-modify-write on steps_json now runs inside a BEGIN IMMEDIATE transaction, closing the race where two parallel branch nodes' observer callbacks could both read the same steps_json and one write clobber the other's step. R-m8: the flow_definitions/flow_runs/flow_suggestions/flow_revisions DDL batch + add_column_if_missing migration now run once per process per database path (a path-keyed cache), instead of on every with_connection call — including every per-node step upsert on every live run. Per-connection pragmas (busy_timeout, foreign_keys) still reapply on every open. Adds a #[cfg(test)] force_corrupt_graph_json_for_test fixture door (mirrors force_run_status_for_test) plus 8 store-level tests and 1 ops-level boot-reconcile test.
c001051 to
4394e0b
Compare
There was a problem hiding this comment.
graycyrus has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4394e0b5a7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if schema_present { | ||
| return Ok(()); |
There was a problem hiding this comment.
Re-run migrations before trusting cached schemas
When this process has already cached a workspace path and the database file is later restored/replaced with an older or partial flows.db, this early return only verifies that flow_definitions exists. It skips init_schema, so missing migrated columns like require_approval (or other tables such as flow_runs/flow_state) are not recreated; the next store call can then fail with no such column: require_approval or no such table until the core restarts. The cache-hit probe should verify the required schema/migrations, or re-run the idempotent migration batch when anything is missing, not just when the primary table is absent.
Useful? React with 👍 / 👎.
Summary
list_flows, allapp_eventtrigger dispatch, and the boot schedule reconcile each hard-failed on the first bad row.Problem
R-M4 (major).
map_flow_rowpropagates migrate/deserialize errors as row errors, andlist_flows/list_enabled_flowsdidflows.push(row?)— so the first bad row failed the whole query.The realistic trigger is a downgrade: a user runs a newer build that persists a graph at a newer
schema_version, then goes back.tinyflows::migrate::migratecannot downgrade, so that single row breaks:flows_list(the Workflows page),list_enabled_flows— which drives allapp_eventtrigger dispatch inbus.rs,reconcile_schedule_triggers_on_boot.The whole flows surface goes down because of one row. The sibling
draft_store::list_draftsalready skips-and-logs corrupt entries; the SQLite store had no such tolerance.R-m1.
upsert_flow_run_stepwas an untransacted read-modify-write (SELECTsteps_json→ mutate in memory → UPDATE) on a fresh connection. Two observer callbacks for parallel branch nodes could interleave — both read[A], one writes[A,B], the other[A,C]— and B vanished from the live view. Itsstatus/duration_mswere lost permanently, since the post-hocsettle_stepsreconstruction refills a node only withstatus: None.R-m8.
with_connectionopened a new connection and re-ran the full DDL batch (5CREATE TABLE+ 6CREATE INDEX+PRAGMA journal_mode=WAL+ aPRAGMA table_infomigration probe) on every call — including the per-step upsert fired for every node of every live run. Idempotent, but real churn on hot runs.Solution
list_flow_rowshelper decodes rows one at a time and skips/logs any that fail to parse or migrate (logging the id and error, nevergraph_json).list_flows/list_enabled_flowsreturn(Vec<Flow>, usize), and the skip count is surfaced rather than swallowed:flows_listputs an "N workflows could not be loaded" line inRpcOutcome.logs, and the boot reconcile +bus.rsapp-event path eachwarn!. A silently short flow list would be a worse failure mode than a hard error, so the count is the point.with_immediate_transactionhelper (BEGIN IMMEDIATE/COMMIT/ROLLBACKthroughexecute_batch, sinceConnection::transaction()needs&mut Connectionand does not fitwith_connection's&Connectionclosure). The existingbusy_timeout = 5000pragma covers the lock wait.OnceLock<Mutex<HashSet<PathBuf>>>. Keyed by path rather than a single global flag, so each test's distinctTempDirworkspace still initializes correctly. Per-connection pragmas (busy_timeout,foreign_keys) still reapply on every open, since those are not persisted in the db file.A
#[cfg(test)] force_corrupt_graph_json_for_testfixture door (mirroring the existingforce_run_status_for_test) stages corrupt/newer-schema rows for the tests.Submission Checklist
cargo test --lib openhuman::flows= 563 passed, 0 failedN/A: resilience fix to existing paths, no feature rows added/removed/renamed## Related—N/A: no matrix feature rows affectedN/A: no release-cut surface behaviour change on the healthy pathCloses #NNN—N/A: found by code review, no tracking issue filed yetImpact
list_flows/list_enabled_flowsnow return(Vec<Flow>, usize). All in-repo callers are updated.BEGIN IMMEDIATEintroduces a short write lock on step upserts; the transaction body is kept minimal and the existing busy timeout covers it.Related
N/Aflows_resumethe run-lifecycle safetyflows_runalready had #5286 — merge that first. Also touchesstore.rs, which docs(flows): fix contract-drift comments and repair the workflow-builder prompt structure #5290 edits; rebase if that lands first.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
fix/flows-store-resilience4618fdf96(plus fix(flows): giveflows_resumethe run-lifecycle safetyflows_runalready had #5286's0b7105fa7as its base)Validation Run
pnpm --filter openhuman-app format:check— N/A, no frontend files changedpnpm typecheck— N/A, no TypeScript changedGGML_NATIVE=OFF cargo test --lib openhuman::flows→ 563 passed, 0 failedGGML_NATIVE=OFF cargo check --manifest-path Cargo.tomlcleanapp/src-tauriuntouchedValidation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
add_column_if_missingmigrations are unchanged.list_enabled_flowsstill gates app-event dispatch identically for decodable rows; only the undecodable-row path differs.Duplicate / Superseded PR Handling