Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions crates/goose/src/session/session_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,8 @@ impl SessionStorage {
let options = SqliteConnectOptions::new()
.filename(db_path)
.create_if_missing(create_if_missing)
.busy_timeout(std::time::Duration::from_secs(5));
.busy_timeout(std::time::Duration::from_secs(5))
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal);

sqlx::SqlitePool::connect_with(options).await.map_err(|e| {
anyhow::anyhow!(
Expand Down Expand Up @@ -605,6 +606,8 @@ impl SessionStorage {
}

async fn import_legacy_session(&self, session: &Session) -> Result<()> {
let mut tx = self.pool.begin().await?;

let recipe_json = match &session.recipe {
Some(recipe) => Some(serde_json::to_string(recipe)?),
None => None,
Expand Down Expand Up @@ -642,9 +645,11 @@ impl SessionStorage {
.bind(&session.schedule_id)
.bind(recipe_json)
.bind(user_recipe_values_json)
.execute(&self.pool)
.execute(&mut *tx)
.await?;

tx.commit().await?;

if let Some(conversation) = &session.conversation {
self.replace_conversation(&session.id, conversation).await?;
}
Comment on lines +651 to 655
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transaction commits before calling replace_conversation, breaking atomicity of the import. If replace_conversation fails, the session will be imported without messages. Consider including the conversation import in the same transaction, or document why this split is intentional.

Suggested change
tx.commit().await?;
if let Some(conversation) = &session.conversation {
self.replace_conversation(&session.id, conversation).await?;
}
if let Some(conversation) = &session.conversation {
self.replace_conversation_tx(&session.id, conversation, &mut tx).await?;
}
tx.commit().await?;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was on the fence with changing this, replace_conversation already does it's own commit independent of import_session. Easiest solution would be to unwrap replace_conversation inside of import_session.

If we move towards foreign keys then messages should not be session orphaned by default and can be addressed there.

Expand Down Expand Up @@ -780,8 +785,10 @@ impl SessionStorage {
name: String,
session_type: SessionType,
) -> Result<Session> {
let mut tx = self.pool.begin().await?;

let today = chrono::Utc::now().format("%Y%m%d").to_string();
Ok(sqlx::query_as(
let session = sqlx::query_as(
r#"
INSERT INTO sessions (id, name, user_set_name, session_type, working_dir, extension_data)
VALUES (
Expand All @@ -804,8 +811,11 @@ impl SessionStorage {
.bind(&name)
.bind(session_type.to_string())
.bind(working_dir.to_string_lossy().as_ref())
.fetch_one(&self.pool)
.await?)
.fetch_one(&mut *tx)
.await?;

tx.commit().await?;
Ok(session)
}

async fn get_session(&self, id: &str, include_messages: bool) -> Result<Session> {
Expand Down Expand Up @@ -931,9 +941,11 @@ impl SessionStorage {
q = q.bind(user_recipe_values_json);
}

let mut tx = self.pool.begin().await?;
q = q.bind(&builder.session_id);
q.execute(&self.pool).await?;
q.execute(&mut *tx).await?;

tx.commit().await?;
Ok(())
}

Expand Down