Skip to content

Commit a09c126

Browse files
Merge pull request #10182 from gitbutlerapp/stop-session-details-crashes
Stop session details errors
2 parents 4219b6a + 0c4d0f8 commit a09c126

File tree

4 files changed

+85
-44
lines changed

4 files changed

+85
-44
lines changed

crates/but-api/src/commands/claude.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::sync::Arc;
22

3+
use anyhow::Context;
34
use but_api_macros::api_cmd;
4-
use but_claude::{ClaudeMessage, ModelType, ThinkingLevel, prompt_templates};
5+
use but_claude::{ClaudeMessage, ModelType, ThinkingLevel, Transcript, prompt_templates};
56
use but_settings::AppSettings;
67
use but_workspace::StackId;
78
use gitbutler_command_context::CommandContext;
@@ -58,21 +59,32 @@ pub fn claude_get_messages(
5859
Ok(messages)
5960
}
6061

61-
#[api_cmd]
6262
#[tauri::command(async)]
6363
#[instrument(err(Debug))]
64-
pub fn claude_get_session_details(
64+
pub async fn claude_get_session_details(
6565
project_id: ProjectId,
6666
session_id: String,
6767
) -> Result<but_claude::ClaudeSessionDetails, Error> {
6868
let project = gitbutler_project::get(project_id)?;
69+
let mut ctx = CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
6970
let session_id = uuid::Uuid::parse_str(&session_id).map_err(anyhow::Error::from)?;
70-
let transcript_path = but_claude::Transcript::get_transcript_path(&project.path, session_id)?;
71-
let transcript = but_claude::Transcript::from_file(&transcript_path)?;
72-
Ok(but_claude::ClaudeSessionDetails {
73-
summary: transcript.summary(),
74-
last_prompt: transcript.prompt(),
75-
})
71+
let session = but_claude::db::get_session_by_id(&mut ctx, session_id)?
72+
.context("Could not find session")?;
73+
let current_id = Transcript::current_valid_session_id(&project.path, &session).await?;
74+
if let Some(current_id) = current_id {
75+
let transcript_path =
76+
but_claude::Transcript::get_transcript_path(&project.path, current_id)?;
77+
let transcript = but_claude::Transcript::from_file(&transcript_path)?;
78+
Ok(but_claude::ClaudeSessionDetails {
79+
summary: transcript.summary(),
80+
last_prompt: transcript.prompt(),
81+
})
82+
} else {
83+
Ok(but_claude::ClaudeSessionDetails {
84+
summary: None,
85+
last_prompt: None,
86+
})
87+
}
7688
}
7789

7890
#[api_cmd]

crates/but-claude/src/bridge.rs

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,10 @@ use serde_json::json;
3333
use std::{
3434
collections::HashMap,
3535
io::{BufRead, BufReader, PipeReader, Read as _},
36-
path::Path,
3736
process::ExitStatus,
3837
sync::Arc,
3938
};
4039
use tokio::{
41-
fs,
4240
process::{Child, Command},
4341
sync::{
4442
Mutex,
@@ -373,22 +371,7 @@ async fn spawn_command(
373371
command.args(["--permission-mode", "acceptEdits"]);
374372
}
375373

376-
let mut session_ids = session.session_ids.clone();
377-
let mut current_id = None;
378-
379-
loop {
380-
if session_ids.is_empty() {
381-
break;
382-
}
383-
384-
let next_id = session_ids.pop();
385-
if let Some(next_id) = next_id
386-
&& transcript_exists_and_likely_valid(&project_path, next_id).await?
387-
{
388-
current_id = Some(next_id);
389-
break;
390-
}
391-
}
374+
let current_id = Transcript::current_valid_session_id(&project_path, &session).await?;
392375

393376
if let Some(current_id) = current_id {
394377
command.args(["--resume", &format!("{current_id}")]);
@@ -400,22 +383,6 @@ async fn spawn_command(
400383
Ok(command.spawn()?)
401384
}
402385

403-
async fn transcript_exists_and_likely_valid(
404-
project_path: &Path,
405-
session_id: uuid::Uuid,
406-
) -> Result<bool> {
407-
let path = Transcript::get_transcript_path(project_path, session_id)?;
408-
if fs::try_exists(&path).await? {
409-
let file = fs::read_to_string(&path).await?;
410-
// Sometimes a transcript gets written out that only as a summary and is
411-
// only 1 line long. These can be considered invalid sessions
412-
if file.lines().count() > 1 {
413-
return Ok(true);
414-
}
415-
}
416-
Ok(false)
417-
}
418-
419386
fn format_message(message: &str, thinking_level: ThinkingLevel) -> String {
420387
match thinking_level {
421388
ThinkingLevel::Normal => message.to_owned(),

crates/but-claude/src/claude_transcript.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use std::{
55
io::BufRead,
66
path::{Path, PathBuf},
77
};
8+
use tokio::fs;
9+
10+
use crate::ClaudeSession;
811

912
#[derive(Debug, Serialize, Deserialize)]
1013
pub struct UserMessage {
@@ -83,6 +86,30 @@ pub struct Transcript {
8386
}
8487

8588
impl Transcript {
89+
pub async fn current_valid_session_id(
90+
path: &Path,
91+
session: &ClaudeSession,
92+
) -> Result<Option<uuid::Uuid>> {
93+
let mut session_ids = session.session_ids.clone();
94+
let mut current_id = None;
95+
96+
loop {
97+
if session_ids.is_empty() {
98+
break;
99+
}
100+
101+
let next_id = session_ids.pop();
102+
if let Some(next_id) = next_id
103+
&& Self::transcript_exists_and_likely_valid(path, next_id).await?
104+
{
105+
current_id = Some(next_id);
106+
break;
107+
}
108+
}
109+
110+
Ok(current_id)
111+
}
112+
86113
pub fn from_file(path: &Path) -> Result<Self> {
87114
let records = Transcript::from_file_raw(path)?
88115
.into_iter()
@@ -174,4 +201,20 @@ impl Transcript {
174201
}
175202
None
176203
}
204+
205+
async fn transcript_exists_and_likely_valid(
206+
project_path: &Path,
207+
session_id: uuid::Uuid,
208+
) -> Result<bool> {
209+
let path = Self::get_transcript_path(project_path, session_id)?;
210+
if fs::try_exists(&path).await? {
211+
let file = fs::read_to_string(&path).await?;
212+
// Sometimes a transcript gets written out that only as a summary and is
213+
// only 1 line long. These can be considered invalid sessions
214+
if file.lines().count() > 1 {
215+
return Ok(true);
216+
}
217+
}
218+
Ok(false)
219+
}
177220
}

crates/but-server/src/lib.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use but_api::{
2121
use but_broadcaster::Broadcaster;
2222
use but_settings::AppSettingsWithDiskSync;
2323
use futures_util::{SinkExt, StreamExt as _};
24+
use gitbutler_project::ProjectId;
2425
use serde::{Deserialize, Serialize};
2526
use serde_json::json;
2627
use tokio::sync::Mutex;
@@ -421,7 +422,25 @@ async fn handle_command(
421422
Err(e) => Err(e),
422423
}
423424
}
424-
"claude_get_session_details" => claude::claude_get_session_details_cmd(request.params),
425+
"claude_get_session_details" => {
426+
#[derive(Deserialize)]
427+
#[serde(rename_all = "camelCase")]
428+
struct Params {
429+
project_id: ProjectId,
430+
session_id: String,
431+
}
432+
let params = serde_json::from_value(request.params).to_error();
433+
match params {
434+
Ok(Params {
435+
project_id,
436+
session_id,
437+
}) => {
438+
let result = claude::claude_get_session_details(project_id, session_id).await;
439+
result.map(|r| json!(r))
440+
}
441+
Err(e) => Err(e),
442+
}
443+
}
425444
"claude_list_permission_requests" => {
426445
claude::claude_list_permission_requests_cmd(request.params)
427446
}

0 commit comments

Comments
 (0)