{"record":{"id":"1d209ad364a62164","repo":"BigPizzaV3/CodexPlusPlus","slug":"global-state-changed-while-provider-sync-was-being-written","errorCode":null,"errorMessage":"global state changed while provider sync was being written","messagePattern":"global state changed while provider sync was being written","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-data/src/provider_sync.rs","lineNumber":5356,"sourceCode":"        for (key, value) in next {\n            state.insert(key, value);\n        }\n        write_global_state_if_unchanged(path, &original_bytes, &state)?;\n    }\n    Ok(count)\n}\n\nconst GLOBAL_STATE_SOURCE_CHANGED_ERROR: &str =\n    \"global state changed while provider sync was being written\";\n\nfn write_global_state_if_unchanged(\n    path: &Path,\n    original_bytes: &[u8],\n    state: &Map<String, Value>,\n) -> anyhow::Result<()> {\n    // 会话删除/撤销也会更新此文件；不得用过期的 provider-sync 快照覆盖新侧边栏条目。\n    if global_state_snapshot(path)?.0 != original_bytes {\n        anyhow::bail!(GLOBAL_STATE_SOURCE_CHANGED_ERROR);\n    }\n    let text = serde_json::to_string_pretty(&Value::Object(state.clone()))?;\n    codex_plus_core::settings::atomic_write(path, text.as_bytes())?;\n    if let Some(parent) = path.parent() {\n        codex_plus_core::settings::atomic_write(\n            &parent.join(\".codex-global-state.json.bak\"),\n            text.as_bytes(),\n        )?;\n    }\n    Ok(())\n}\n\nfn path_array(value: &Value) -> Vec<String> {\n    if let Some(items) = value.as_array() {\n        items\n            .iter()\n            .filter_map(Value::as_str)\n            .filter(|item| !item.trim().is_empty())","sourceCodeStart":5338,"sourceCodeEnd":5374,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-data/src/provider_sync.rs#L5338-L5374","documentation":"write_global_state (persisting a provider-sync snapshot into .codex-global-state.json) re-reads the file before writing and bails if its contents no longer match the original bytes captured earlier, because thread deletion/undo may have updated the file in the meantime. Writing would overwrite new sidebar entries with a stale provider-sync snapshot. The constant GLOBAL_STATE_SOURCE_CHANGED_ERROR carries this message.","triggerScenarios":"Calling provider-sync save/apply while .codex-global-state.json was modified between global_state_snapshot() and the write — typically by a concurrent thread deletion or undo operation that rewrote the same file.","commonSituations":"User deletes a thread (or performs undo) in the app while a provider sync refresh is saving; automated sync interval racing a user action; two app windows performing operations on the same codex_home.","solutions":["Retry: re-take the global state snapshot and re-run the provider-sync write against fresh bytes.","Avoid running provider sync and thread delete/undo simultaneously; sequence them (await the other operation).","Use a lock around global-state mutations so snapshot-compare-write is atomic per process.","Merge into the current file content (re-read state, apply provider-sync keys only) rather than overwriting the whole object from a stale snapshot."],"exampleFix":"// before\nif global_state_snapshot(path)?.0 != original_bytes {\n    anyhow::bail!(GLOBAL_STATE_SOURCE_CHANGED_ERROR);\n}\n// after\nlet mut original_bytes = original_bytes.to_vec();\nfor _ in 0..3 {\n    match global_state_snapshot(path) {\n        Ok((b, state)) if b == original_bytes => return persist(path, state, extra),\n        Ok((b, _)) => original_bytes = b, // refresh snapshot and retry\n        Err(e) => return Err(e),\n    }\n}","handlingStrategy":"retry","validationCode":"let (_snap, _state) = global_state_snapshot(path)?;\n// re-check right before writing:\nif fs::read(path)? != _snap {\n    eprintln!(\"global state changed; refresh snapshot before provider sync write\");\n}","typeGuard":null,"tryCatchPattern":"loop {\n    match save_provider_sync(codex_home) {\n        Ok(()) => break,\n        Err(e) if e.to_string().contains(GLOBAL_STATE_SOURCE_CHANGED_ERROR) => {\n            if retries >= 3 { break Err(e) }\n            retries += 1; // re-snapshot and retry\n        }\n        Err(e) => break Err(e),\n    }\n}","preventionTips":["Avoid overlapping provider-sync saves with thread delete/undo operations","Take a fresh global_state_snapshot immediately before each write","Use a mutex/lock file for all writers of .codex-global-state.json","Prefer merging only the provider-sync keys over overwriting the full object"],"tags":["concurrency","file-write","state-management"],"backgroundTag":"invalid-state-transition","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}