{"record":{"id":"e970c1231dcfaf39","repo":"BigPizzaV3/CodexPlusPlus","slug":"restore-conflict-agent-job-items-row-already-assi","errorCode":null,"errorMessage":"restore conflict: agent_job_items row already assigned","messagePattern":"restore conflict: agent_job_items row already assigned","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-data/src/storage.rs","lineNumber":1174,"sourceCode":"    let Some(id) = row.get(\"id\") else {\n        return Ok(false);\n    };\n    if !row.contains_key(\"assigned_thread_id\") || !has_table(db, \"agent_job_items\")? {\n        return Ok(false);\n    }\n    let id_value = OwnedSqlValue(json_to_sql_value(id));\n    let current_assignment = db.query_row(\n        \"SELECT assigned_thread_id FROM agent_job_items WHERE id = ?1 LIMIT 1\",\n        [&id_value as &dyn ToSql],\n        |row| row.get::<_, Option<String>>(0),\n    );\n    let current_assignment = match current_assignment {\n        Ok(value) => value,\n        Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(false),\n        Err(err) => return Err(err.into()),\n    };\n    if current_assignment.is_some() {\n        anyhow::bail!(\"restore conflict: agent_job_items row already assigned\");\n    }\n    let assigned = OwnedSqlValue(json_to_sql_value(&row[\"assigned_thread_id\"]));\n    db.execute(\n        \"UPDATE agent_job_items SET assigned_thread_id = ?1 WHERE id = ?2 AND assigned_thread_id IS NULL\",\n        [&assigned as &dyn ToSql, &id_value as &dyn ToSql],\n    )?;\n    Ok(true)\n}\n\nfn backup_related_rows(\n    db: &Connection,\n    tables: &mut Map<String, Value>,\n    table: &str,\n    where_clause: &str,\n    params: &[&dyn ToSql],\n) -> anyhow::Result<()> {\n    if has_table(db, table)? {\n        let rows = select_dicts(","sourceCodeStart":1156,"sourceCodeEnd":1192,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/f2074595a281bc057525c748175c8eb9805b0673/crates/codex-plus-data/src/storage.rs#L1156-L1192","documentation":"Thrown by detect_restore_conflicts's agent_job_items check (surfaced via restore_rows) when a backed-up agent_job_items row's id is already assigned to a different thread (assigned_thread_id is set and differs). Job items are single-owner; the offending input is the conflicting assignment in the live agent_job_items table, and the restore is refused rather than reassigning the job.","triggerScenarios":"Thrown at crates/codex-plus-data/src/storage.rs:1174 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["确认该 job 条目当前是否已被新会话使用，若是则放弃恢复该行","先解除现有 assigned_thread_id 赋值再执行恢复","检查是否重复消费了同一 undo token","对 agent_job_items 实现按行跳过而非整体失败的恢复策略"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"f2074595a281bc057525c748175c8eb9805b0673","analyzedAt":"2026-08-23T12:52:24.489Z","contentChangedAt":"2026-08-23T12:52:24.489Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}