{"record":{"id":"e66438d97871e04b","repo":"tinyhumansai/openhuman","slug":"tool-set-user-state-failed-e","errorCode":null,"errorMessage":"{tool}: set_user_state failed: {e:#}","messagePattern":"(.+?): set_user_state failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/agent/learning/tools.rs","lineNumber":273,"sourceCode":"        Ok(ToolResult::success(serde_json::to_string(&json!({\n            \"facet\": facet_to_json(&facet),\n        }))?))\n    }\n}\n\n/// Set/clear a facet's pin via `set_user_state`. Shared by pin/unpin.\nasync fn set_pin(\n    args: serde_json::Value,\n    tool: &str,\n    state: UserState,\n) -> anyhow::Result<ToolResult> {\n    let class_str = read_required_str(&args, \"class\")?;\n    let key_suffix = read_required_str(&args, \"key\")?;\n    let fk = full_key(&class_str, &key_suffix);\n    let cache = get_cache()?;\n    let updated = cache\n        .set_user_state(&fk, state)\n        .map_err(|e| anyhow::anyhow!(\"{tool}: set_user_state failed: {e:#}\"))?;\n    if !updated {\n        return Err(anyhow::anyhow!(\"{tool}: facet not found: {fk}\"));\n    }\n    let facet = cache\n        .get(&fk)\n        .map_err(|e| anyhow::anyhow!(\"{tool}: re-read failed: {e:#}\"))?;\n    Ok(ToolResult::success(serde_json::to_string(&json!({\n        \"facet\": facet.as_ref().map(facet_to_json),\n    }))?))\n}\n\n/// Pin a facet. Default-OFF.\npub struct LearningPinFacetTool;\n\n#[async_trait]\nimpl Tool for LearningPinFacetTool {\n    fn name(&self) -> &str {\n        \"learning_pin_facet\"","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/src/openhuman/agent/learning/tools.rs#L255-L291","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match tool_exec(&tool_name, args).await { // learning_pin_facet | learning_unpin_facet\n    Err(e) if e.to_string().contains(\"set_user_state failed\") => {\n        tokio::time::sleep(Duration::from_millis(300)).await;\n        tool_exec(&tool_name, args).await // UPDATE is idempotent\n    }\n    other => other,\n}","preventionTips":["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"],"tags":["rust","sqlite","learning","agent-tools","pinning"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}