tinyhumansai/openhuman · error · anyhow::Error
Failed to add {what}
Error message
Failed to add {what} What it means
A column-add migration on the MCP registry SQLite store failed with something other than the deliberately-swallowed 'duplicate column name' race. Because each store call opens its own connection and runs init_schema, concurrent RPCs race to ALTER TABLE; SQLite ADD COLUMN has no IF NOT EXISTS, so the duplicate-column loser is expected and swallowed. This message means a different ALTER failure — the index/migration is genuinely broken (locked DB, disk error, incompatible schema).
Source
Thrown at src/openhuman/mcp/registry/store.rs:140
/// common case, but that check-then-alter is not atomic *across connections*:
/// every store call opens its own [`Connection`] and runs `init_schema`, so the
/// several MCP RPCs a single page load fans out (list / status / registry) can
/// each snapshot the column as missing before any of them adds it — then all
/// race to `ALTER`, and every loser fails with "duplicate column name". SQLite's
/// `ADD COLUMN` has no `IF NOT EXISTS`, so we swallow exactly that error: the
/// column existing is the desired post-condition, and surfacing it turned a
/// benign race into the red "Failed to add deployment_url column to mcp_servers"
/// banner on the MCP Servers page (#4194). Any other failure still propagates.
fn add_column_idempotent(conn: &Connection, ddl: &str, what: &str) -> Result<()> {
match conn.execute(ddl, []) {
Ok(_) => Ok(()),
Err(rusqlite::Error::SqliteFailure(_, Some(msg)))
if msg.contains("duplicate column name") =>
{
log::debug!("[mcp_registry] {what} already present (concurrent migration) — skipping");
Ok(())
}
Err(e) => Err(anyhow::Error::new(e).context(format!("Failed to add {what}"))),
}
}
/// Snapshot of the column names on `mcp_servers`. Used by the additive
/// migration in [`init_schema`] to decide which `ALTER TABLE ADD COLUMN`
/// statements still need to run on this DB.
fn mcp_servers_columns(conn: &Connection) -> Result<Vec<String>> {
let mut stmt = conn
.prepare("PRAGMA table_info(mcp_servers)")
.context("prepare PRAGMA table_info")?;
// PRAGMA table_info row shape: (cid, name, type, notnull, dflt_value, pk).
let mut rows = stmt.query([])?;
let mut cols = Vec::new();
while let Some(row) = rows.next()? {
let name: String = row.get(1)?;
cols.push(name);
}
Ok(cols)View on GitHub (pinned to 7491200858)
Solutions
- Inspect the underlying SQLite error logged alongside to distinguish lock/IO from schema incompatibility
- If the DB is locked by concurrent init, serialize store access or enable busy_timeout/WAL
- Restore or recreate the mcp registry database if its schema is corrupted beyond the migration's reach
Defensive patterns
Strategy: try-catch
When it happens
Trigger: Thrown at src/openhuman/mcp/registry/store.rs:140 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/8bd099137b0b3e39.
Report an issue: GitHub.