RightNow-AI/openfang · error

{e}

Error message

{e}

What it means

The extension uninstall HTTP route acquires the extension_registry write lock (tolerating poisoning via into_inner) and calls registry.uninstall(&id). When uninstall returns an Err, the route responds 404 NOT_FOUND with the error serialized as {"error": ...} — meaning the registry could not uninstall that extension id.

Source

Thrown at crates/openfang-api/src/routes.rs:9015

            "connected": connected > 0,
            "message": format!("Integration '{}' installed", id),
        })),
    )
}

/// DELETE /api/integrations/:id — Remove an integration.
pub async fn remove_integration(
    State(state): State<Arc<AppState>>,
    Path(id): Path<String>,
) -> impl IntoResponse {
    // Scope the write lock
    let uninstall_err = {
        let mut registry = state
            .kernel
            .extension_registry
            .write()
            .unwrap_or_else(|e| e.into_inner());
        registry.uninstall(&id).err()
    };

    if let Some(e) = uninstall_err {
        return (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({"error": e.to_string()})),
        );
    }

    state.kernel.extension_health.unregister(&id);

    // Hot-disconnect the removed MCP server
    let _ = state.kernel.reload_extension_mcps().await;

    (
        StatusCode::OK,
        Json(serde_json::json!({
            "id": id,

View on GitHub (pinned to acf2587e46)

Solutions

  1. Confirm the extension id exists via the registry list endpoint before uninstalling.
  2. Make the client treat 404 as idempotent success for repeat uninstall calls.
  3. Read the JSON error body — it carries the registry's specific reason (e.g. 'extension not found').
  4. If ids change on reinstall, look up the current id by extension name instead of caching it.

Example fix

// before
let res = client.delete(&format!("/extensions/{id}/uninstall")).send().await?;
res.error_for_status()?;
// after
let res = client.delete(&format!("/extensions/{id}/uninstall")).send().await?;
match res.status() {
    StatusCode::OK => Ok(()),
    StatusCode::NOT_FOUND => Ok(()), // already uninstalled — idempotent
    _ => Err(anyhow!(res.text().await?)),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Client-side: confirm the extension exists before uninstalling
let list = client.get("/extensions").send().await?.json::<Vec<Extension>>().await?;
if !list.iter().any(|x| x.id == id) {
    return Ok(()); // nothing to uninstall — skip
}

Type guard

fn is_uninstall_not_found(status: StatusCode, body: &serde_json::Value) -> bool {
    status == StatusCode::NOT_FOUND && body["error"].is_string()
}

Try / catch

// Rust (HTTP client)
let res = client.delete(&format!("/extensions/{id}/uninstall")).send().await?;
match res.status() {
    StatusCode::OK => Ok(()),
    StatusCode::NOT_FOUND => Ok(()), // treat as idempotent success
    _ => Err(anyhow!("uninstall failed: {}", res.text().await?)),
}

Prevention

When it happens

Trigger: DELETE/POST to the uninstall endpoint with an extension id that is not currently installed, or an id the registry refuses to uninstall (unknown id producing an internal error).

Common situations: Extension already uninstalled in a prior request (double-delete); client using a stale id after a reinstall changed ids; typo'd extension id in the request path/body.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.


AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02). Data as JSON: /api/errors/ebd106c468760ae4. Report an issue: GitHub.