tinyhumansai/openhuman · error
{tool}: set_user_state failed: {e:#}
Error message
{tool}: set_user_state failed: {e:#} What it means
Thrown from the shared set_pin helper used by both learning_pin_facet and learning_unpin_facet ({tool} interpolates to one of those names) when FacetCache::set_user_state() returns an Err. set_user_state is a single SQL UPDATE on user_profile; an Err means the UPDATE itself failed (locked DB, I/O), as opposed to returning Ok(false) which is the 'facet not found' case.
Source
Thrown at src/openhuman/agent/learning/tools.rs:273
Ok(ToolResult::success(serde_json::to_string(&json!({
"facet": facet_to_json(&facet),
}))?))
}
}
/// Set/clear a facet's pin via `set_user_state`. Shared by pin/unpin.
async fn set_pin(
args: serde_json::Value,
tool: &str,
state: UserState,
) -> anyhow::Result<ToolResult> {
let class_str = read_required_str(&args, "class")?;
let key_suffix = read_required_str(&args, "key")?;
let fk = full_key(&class_str, &key_suffix);
let cache = get_cache()?;
let updated = cache
.set_user_state(&fk, state)
.map_err(|e| anyhow::anyhow!("{tool}: set_user_state failed: {e:#}"))?;
if !updated {
return Err(anyhow::anyhow!("{tool}: facet not found: {fk}"));
}
let facet = cache
.get(&fk)
.map_err(|e| anyhow::anyhow!("{tool}: re-read failed: {e:#}"))?;
Ok(ToolResult::success(serde_json::to_string(&json!({
"facet": facet.as_ref().map(facet_to_json),
}))?))
}
/// Pin a facet. Default-OFF.
pub struct LearningPinFacetTool;
#[async_trait]
impl Tool for LearningPinFacetTool {
fn name(&self) -> &str {
"learning_pin_facet"View on GitHub (pinned to a221052e0d)
Solutions
- Retry the pin/unpin after the lock clears — the UPDATE is idempotent
- Confirm the key exists with learning_get_facet to rule out the not-found variant
- Check for concurrent learning_rebuild_cache / learning_reset_cache calls and serialize them
- Inspect the error chain for rusqlite specifics (busy vs corrupt vs disk I/O)
Defensive patterns
Strategy: retry
Try / catch
match tool_exec(&tool_name, args).await { // learning_pin_facet | learning_unpin_facet
Err(e) if e.to_string().contains("set_user_state failed") => {
tokio::time::sleep(Duration::from_millis(300)).await;
tool_exec(&tool_name, args).await // UPDATE is idempotent
}
other => other,
} Prevention
- Pin/unpin are state-setting UPDATEs — safe to retry; not-found is the signal to stop and re-list keys
- Avoid pinning during a rebuild cycle; rebuilds evict non-pinned rows you may be about to pin
When it happens
Trigger: Invoking learning_pin_facet / learning_unpin_facet with {class, key} while the SQLite DB is locked by a rebuild cycle, reset, or reflection persist; storage I/O failure. Distinguished from a wrong key by the error chain carrying a store error rather than the not-found message.
Common situations: Agent pins a facet immediately after heavy transcript ingest triggered the stability cycle; workspace DB on a flaky disk; concurrent UI RPC calls into the learning namespace.
Related errors
- learning_update_facet: {e:#}
- learning_update_facet: upsert failed: {e:#}
- {tool}: re-read failed: {e:#}
- learning_forget_facet: {e:#}
- learning_forget_facet: upsert failed: {e:#}
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/e66438d97871e04b.
Report an issue: GitHub.