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
- Confirm the extension id exists via the registry list endpoint before uninstalling.
- Make the client treat 404 as idempotent success for repeat uninstall calls.
- Read the JSON error body — it carries the registry's specific reason (e.g. 'extension not found').
- 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
- Make uninstall calls idempotent — ignore 404 on repeat deletes.
- List extensions and resolve the id by name instead of caching ids.
- Avoid double-submitting the uninstall request from UI code.
- Read the JSON error body for the registry's specific reason.
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.