Download codex-rs/cli/src/state_db_recovery.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 6.31 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/cli/src/state_db_recovery.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/cli/src/state_db_recovery.rs
-
curl -L -o state_db_recovery.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/cli/src/state_db_recovery.rs
6.31 kB
| //! CLI handling for local state database startup failures. | |
| //! | |
| //! This keeps user-facing backup and lock-contention handling out of the main | |
| //! CLI dispatch path while preserving the TUI startup error as the boundary type. | |
| use codex_state::RuntimeDbBackup; | |
| use codex_tui::LocalStateDbStartupError; | |
| use std::io::IsTerminal; | |
| use std::path::Path; | |
| pub(crate) fn startup_error(err: &std::io::Error) -> Option<&LocalStateDbStartupError> { | |
| err.get_ref() | |
| .and_then(|err| err.downcast_ref::<LocalStateDbStartupError>()) | |
| } | |
| pub(crate) fn is_locked(detail: &str) -> bool { | |
| codex_state::sqlite_error_detail_is_lock(detail) | |
| } | |
| pub(crate) fn is_corruption(detail: &str) -> bool { | |
| codex_state::sqlite_error_detail_is_corruption(detail) | |
| } | |
| pub(crate) fn is_auto_backup_recoverable(startup_error: &LocalStateDbStartupError) -> bool { | |
| is_corruption(startup_error.detail()) || sqlite_home_is_blocking_file(startup_error) | |
| } | |
| fn sqlite_home_is_blocking_file(startup_error: &LocalStateDbStartupError) -> bool { | |
| startup_error | |
| .database_path() | |
| .parent() | |
| .and_then(|path| std::fs::metadata(path).ok()) | |
| .is_some_and(|metadata| metadata.is_file()) | |
| } | |
| pub(crate) fn print_auto_backup_start(startup_error: &LocalStateDbStartupError) { | |
| eprintln!("Codex couldn't start because its local database appears to be damaged."); | |
| eprintln!("Moving the damaged local database aside so Codex can rebuild it from saved data."); | |
| print_technical_details(startup_error); | |
| } | |
| pub(crate) async fn backup_files_for_fresh_start( | |
| startup_error: &LocalStateDbStartupError, | |
| ) -> std::io::Result<Vec<RuntimeDbBackup>> { | |
| codex_state::backup_runtime_db_for_fresh_start(startup_error.database_path()).await | |
| } | |
| pub(crate) fn confirm_fresh_start_rebuild( | |
| startup_error: &LocalStateDbStartupError, | |
| backups: &[RuntimeDbBackup], | |
| ) -> std::io::Result<()> { | |
| eprintln!("Codex rebuilt its local database."); | |
| eprintln!( | |
| "Codex detected a damaged local database, moved it into a backup folder, and will continue startup with a fresh database." | |
| ); | |
| eprintln!("Database path: {}", startup_error.database_path().display()); | |
| if let Some(backup_folder) = backup_folder(backups) { | |
| eprintln!("Backup folder: {}", backup_folder.display()); | |
| } else { | |
| eprintln!("Backup folder: unavailable"); | |
| } | |
| if std::io::stdin().is_terminal() && std::io::stderr().is_terminal() { | |
| eprintln!("Press Enter to continue."); | |
| let mut input = String::new(); | |
| std::io::stdin().read_line(&mut input)?; | |
| } else { | |
| eprintln!("Continuing startup with a fresh local database..."); | |
| } | |
| Ok(()) | |
| } | |
| pub(crate) fn print_diagnostic_guidance(startup_error: &LocalStateDbStartupError) { | |
| eprintln!("Codex couldn't start because its local database appears to be damaged."); | |
| eprintln!("Run `codex doctor` to check your setup and get next-step guidance."); | |
| eprintln!("If this keeps happening, share the technical details below when asking for help."); | |
| print_technical_details(startup_error); | |
| } | |
| pub(crate) fn print_locked_guidance(startup_error: &LocalStateDbStartupError) { | |
| eprintln!("Codex couldn't start because another Codex process is using its local data."); | |
| eprintln!("Quit any other copies of Codex that may still be running, then try again."); | |
| print_technical_details(startup_error); | |
| } | |
| fn print_technical_details(startup_error: &LocalStateDbStartupError) { | |
| eprintln!("Technical details:"); | |
| eprintln!(" Location: {}", startup_error.database_path().display()); | |
| eprintln!(" Cause: {}", startup_error.detail()); | |
| } | |
| fn backup_folder(backups: &[RuntimeDbBackup]) -> Option<&Path> { | |
| backups.first()?.backup_path.parent() | |
| } | |
| mod tests { | |
| use super::*; | |
| use codex_utils_absolute_path::test_support::PathExt; | |
| use pretty_assertions::assert_eq; | |
| use std::path::PathBuf; | |
| use tempfile::TempDir; | |
| async fn backup_backs_up_only_failed_database_file() -> std::io::Result<()> { | |
| let temp_dir = TempDir::new()?; | |
| let sqlite = codex_state::SqliteConfig::new_for_testing(temp_dir.path().abs()); | |
| let state_path = sqlite.state_db_path(); | |
| let failed_db_path = sqlite.logs_db_path(); | |
| tokio::fs::write(state_path.as_path(), b"state").await?; | |
| tokio::fs::write(failed_db_path.as_path(), b"logs").await?; | |
| let startup_error = | |
| LocalStateDbStartupError::new(failed_db_path.clone(), "corrupt".to_string()); | |
| let backups = backup_files_for_fresh_start(&startup_error).await?; | |
| assert_eq!( | |
| backups | |
| .iter() | |
| .map(|backup| &backup.original_path) | |
| .collect::<Vec<_>>(), | |
| vec![&failed_db_path] | |
| ); | |
| assert!(!tokio::fs::try_exists(failed_db_path.as_path()).await?); | |
| assert!(tokio::fs::try_exists(state_path.as_path()).await?); | |
| assert!(tokio::fs::try_exists(backups[0].backup_path.as_path()).await?); | |
| Ok(()) | |
| } | |
| async fn backup_replaces_blocking_sqlite_home_file() -> std::io::Result<()> { | |
| let temp_dir = TempDir::new()?; | |
| let sqlite_home = temp_dir.path().join("sqlite-home"); | |
| tokio::fs::write(sqlite_home.as_path(), b"not-a-directory").await?; | |
| let sqlite = codex_state::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()); | |
| let startup_error = | |
| LocalStateDbStartupError::new(sqlite.state_db_path(), "File exists".to_string()); | |
| assert!(is_auto_backup_recoverable(&startup_error)); | |
| let backups = backup_files_for_fresh_start(&startup_error).await?; | |
| assert_eq!(backups.len(), 1); | |
| assert!(tokio::fs::metadata(sqlite_home.as_path()).await?.is_dir()); | |
| assert!(tokio::fs::try_exists(backups[0].backup_path.as_path()).await?); | |
| Ok(()) | |
| } | |
| fn backup_folder_uses_parent_of_first_backup_path() { | |
| let backups = vec![RuntimeDbBackup { | |
| original_path: PathBuf::from("/tmp/state_5.sqlite"), | |
| backup_path: PathBuf::from("/tmp/db-backups/sqlite-1-0/state_5.sqlite"), | |
| }]; | |
| assert_eq!( | |
| backup_folder(&backups), | |
| Some(Path::new("/tmp/db-backups/sqlite-1-0")) | |
| ); | |
| } | |
| } | |