tinyhumansai/openhuman · error

flow '{id}' not found

Error message

flow '{id}' not found

What it means

remove_flow's DELETE FROM flow_definitions affected zero rows, so no flow had that id and the delete bails instead of pretending success. Like its cron twin, the affected-row-count check distinguishes 'deleted' from 'nothing there'.

Source

Thrown at src/openhuman/flows/store.rs:449

/// `ComposioTriggerReceived` event against every enabled `app_event` flow —
/// scanning the (small) enabled set once per event is simpler and cheap
/// enough at expected flow counts; a dedicated toolkit/trigger_slug index is
/// a later optimization if this ever shows up as a bottleneck.
///
/// Returns `(flows, skipped)` — see [`list_flows`]. A corrupt row here must
/// not take down `app_event` dispatch for every *other* enabled flow (R-M4).
pub fn list_enabled_flows(config: &Config) -> Result<(Vec<Flow>, usize)> {
    with_connection(config, |conn| list_flow_rows(conn, "WHERE enabled = 1"))
}

/// Deletes a flow by id. Returns an error if no such flow exists.
pub fn remove_flow(config: &Config, id: &str) -> Result<()> {
    let changed = with_connection(config, |conn| {
        conn.execute("DELETE FROM flow_definitions WHERE id = ?1", params![id])
            .context("Failed to delete flow definition")
    })?;
    if changed == 0 {
        anyhow::bail!("flow '{id}' not found");
    }
    tracing::debug!(flow_id = %id, "[flows] removed flow definition");
    Ok(())
}

/// Toggles a flow's `enabled` flag, returning the updated record.
pub fn set_enabled(config: &Config, id: &str, enabled: bool) -> Result<Flow> {
    let now = Utc::now().to_rfc3339();
    let changed = with_connection(config, |conn| {
        conn.execute(
            "UPDATE flow_definitions SET enabled = ?1, updated_at = ?2 WHERE id = ?3",
            params![if enabled { 1 } else { 0 }, now, id],
        )
        .context("Failed to update flow enabled state")
    })?;
    if changed == 0 {
        anyhow::bail!("flow '{id}' not found");
    }

View on GitHub (pinned to 7491200858)

Solutions

  1. List flows (flows.list) and confirm the id still exists; if gone, treat the delete as done
  2. Re-copy the id from the current listing if the flow exists under a different id
  3. Verify workspace/user profile if the listing itself is empty

Example fix

// before
await flowsApi.remove(flowId); // throws on already-deleted

// after
try { await flowsApi.remove(flowId); }
catch (e) { if (!isNotFoundFlowError(e)) throw e; } // idempotent delete
Defensive patterns

Strategy: try-catch

Try / catch

try { await removeFlow(id); } catch (e) { if (isNotFoundFlowError(e)) return ok('already deleted'); throw e; } — classify by the 'flow ... not found' message and treat it as success for idempotent deletes.

Prevention

When it happens

Trigger: flows.delete with a stale id (flow already removed from another session or tab); a typo'd id; a different workspace's flow store, which is per-workspace SQLite like cron.

Common situations: Double-submit of a delete button; retry after a timeout whose first attempt succeeded; scripts holding ids from an earlier listing; multiple open clients.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/8d5a5c1a674fa85b. Report an issue: GitHub.