tinyhumansai/openhuman · error
learning_update_facet: upsert failed: {e:#}
Error message
learning_update_facet: upsert failed: {e:#} What it means
Thrown by learning_update_facet when the facet was read and mutated in memory but FacetCache::upsert() (ProfileStore::upsert_full) failed to persist it. It is the write-path twin of the get error: the value change and the UserState::Pinned transition were NOT saved. Causes are SQLite write failures — busy database, disk full, constraint/I/O errors.
Source
Thrown at src/openhuman/agent/learning/tools.rs:254
PermissionLevel::Write
}
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
log::debug!("[tool][learning] update_facet invoked");
let class_str = read_required_str(&args, "class")?;
let key_suffix = read_required_str(&args, "key")?;
let value = read_required_str(&args, "value")?;
let fk = full_key(&class_str, &key_suffix);
let cache = get_cache()?;
let mut facet = cache
.get(&fk)
.map_err(|e| anyhow::anyhow!("learning_update_facet: {e:#}"))?
.ok_or_else(|| anyhow::anyhow!("learning_update_facet: facet not found: {fk}"))?;
facet.value = value;
facet.user_state = UserState::Pinned;
cache
.upsert(&facet)
.map_err(|e| anyhow::anyhow!("learning_update_facet: upsert failed: {e:#}"))?;
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)View on GitHub (pinned to a221052e0d)
Solutions
- Retry the update once the concurrent writer (rebuild/reset/reflection) finishes
- Verify the facet's persisted state afterwards with learning_get_facet — if the old value is still there the upsert truly did not land
- Check disk space and workspace_dir DB permissions
- Ensure only one core process uses the workspace (OPENHUMAN_CORE_REUSE_EXISTING debugging leftovers are a classic double-open)
Defensive patterns
Strategy: retry
Try / catch
let res = tool_exec("learning_update_facet", args).await;
if let Err(e) = &res {
if e.to_string().contains("upsert failed") && e.to_string().contains("locked") {
tokio::time::sleep(Duration::from_millis(500)).await;
return tool_exec("learning_update_facet", args).await; // idempotent value write
}
}
res Prevention
- After any reported upsert failure, verify with learning_get_facet whether the value landed before re-sending
- Do not interleave learning_rebuild_cache and facet updates in the same turn
- Watch disk space — full disks turn transient lock errors into persistent I/O errors
When it happens
Trigger: learning_update_facet executes its UPDATE while a stability rebuild or learning_reset_cache is writing the same table; disk full; DB opened read-only or corrupted. The tool had already fetched the facet successfully, so the read path was healthy at that moment.
Common situations: Update racing learning_rebuild_cache or learning_forget_facet on the same key; low-disk laptops; a second core instance pointed at the same workspace_dir.
Related errors
- learning_forget_facet: upsert failed: {e:#}
- learning_update_facet: {e:#}
- {tool}: set_user_state failed: {e:#}
- {tool}: re-read failed: {e:#}
- learning_forget_facet: {e:#}
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/8ae967f02bb9392b.
Report an issue: GitHub.