Skip to content

Commit af814a0

Browse files
Merge pull request #9929 from gitbutlerapp/add-clause-session-in-gui-flag
Fix existing hooker configs screwing up when working through the gui
2 parents b1b1e83 + bafe628 commit af814a0

File tree

10 files changed

+97
-5
lines changed

10 files changed

+97
-5
lines changed

crates/but-claude/src/bridge.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,9 +311,10 @@ async fn upsert_session(
311311
) -> Result<crate::ClaudeSession> {
312312
let mut ctx = ctx.lock().await;
313313
let session = if let Some(session) = db::get_session_by_id(&mut ctx, session_id)? {
314+
db::set_session_in_gui(&mut ctx, session_id, true)?;
314315
session
315316
} else {
316-
let session = db::save_new_session(&mut ctx, session_id)?;
317+
let session = db::save_new_session_with_gui_flag(&mut ctx, session_id, true)?;
317318
create_claude_assignment_rule(&mut ctx, session_id, stack_id)?;
318319
session
319320
};

crates/but-claude/src/claude_config.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use serde_json::json;
66

77
/// Formats the claude code config
88
pub fn fmt_claude_settings() -> Result<String> {
9-
let cli_cmd = format!("\"{}\"", get_cli_path()?.to_string_lossy());
9+
let cli_cmd = format!(
10+
"GITBUTLER_IN_GUI=1 \"{}\"",
11+
get_cli_path()?.to_string_lossy()
12+
);
1013
let pre_cmd = format!("{cli_cmd} claude pre-tool");
1114
let post_cmd = format!("{cli_cmd} claude post-tool");
1215
let stop_cmd = format!("{cli_cmd} claude stop");

crates/but-claude/src/db.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@ use crate::{ClaudePermissionRequest, ClaudeSession};
66

77
/// Creates a new ClaudeSession with the session_id provided and saves it to the database.
88
pub fn save_new_session(ctx: &mut CommandContext, id: Uuid) -> anyhow::Result<ClaudeSession> {
9+
save_new_session_with_gui_flag(ctx, id, false)
10+
}
11+
12+
/// Creates a new ClaudeSession with the session_id provided and saves it to the database.
13+
pub fn save_new_session_with_gui_flag(
14+
ctx: &mut CommandContext,
15+
id: Uuid,
16+
in_gui: bool,
17+
) -> anyhow::Result<ClaudeSession> {
918
let now = chrono::Utc::now().naive_utc();
1019
let session = ClaudeSession {
1120
id,
1221
current_id: id,
1322
created_at: now,
1423
updated_at: now,
24+
in_gui,
1525
};
1626
ctx.db()?
1727
.claude_sessions()
@@ -27,7 +37,19 @@ pub fn set_session_current_id(
2737
) -> anyhow::Result<()> {
2838
ctx.db()?
2939
.claude_sessions()
30-
.update(&session_id.to_string(), &current_id.to_string())?;
40+
.update_current_id(&session_id.to_string(), &current_id.to_string())?;
41+
Ok(())
42+
}
43+
44+
/// Updates the current session ID for a given session in the database.
45+
pub fn set_session_in_gui(
46+
ctx: &mut CommandContext,
47+
session_id: Uuid,
48+
in_gui: bool,
49+
) -> anyhow::Result<()> {
50+
ctx.db()?
51+
.claude_sessions()
52+
.update_in_gui(&session_id.to_string(), in_gui)?;
3153
Ok(())
3254
}
3355

@@ -140,6 +162,7 @@ impl TryFrom<but_db::ClaudeSession> for crate::ClaudeSession {
140162
current_id: Uuid::parse_str(&value.current_id)?,
141163
created_at: value.created_at,
142164
updated_at: value.updated_at,
165+
in_gui: value.in_gui,
143166
})
144167
}
145168
}
@@ -152,6 +175,7 @@ impl TryFrom<crate::ClaudeSession> for but_db::ClaudeSession {
152175
current_id: value.current_id.to_string(),
153176
created_at: value.created_at,
154177
updated_at: value.updated_at,
178+
in_gui: value.in_gui,
155179
})
156180
}
157181
}

crates/but-claude/src/hooks/mod.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub struct ClaudeStopInput {
8888
pub async fn handle_stop() -> anyhow::Result<ClaudeHookOutput> {
8989
let input: ClaudeStopInput = serde_json::from_str(&stdin()?)
9090
.map_err(|e| anyhow::anyhow!("Failed to parse input JSON: {}", e))?;
91+
9192
let transcript = Transcript::from_file(Path::new(&input.transcript_path))?;
9293
let cwd = transcript.dir()?;
9394
let repo = gix::discover(cwd)?;
@@ -119,6 +120,14 @@ pub async fn handle_stop() -> anyhow::Result<ClaudeHookOutput> {
119120
let ctx = &mut CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
120121
let session_id = original_session_id(ctx, input.session_id.clone())?;
121122

123+
if should_exit_early(ctx, &input.session_id)? {
124+
return Ok(ClaudeHookOutput {
125+
do_continue: true,
126+
stop_reason: "Session running in GUI, skipping hook".to_string(),
127+
suppress_output: true,
128+
});
129+
}
130+
122131
let defer = ClearLocksGuard {
123132
ctx,
124133
session_id: session_id.clone(),
@@ -295,6 +304,7 @@ pub struct ClaudePreToolUseInput {
295304
pub fn handle_pre_tool_call() -> anyhow::Result<ClaudeHookOutput> {
296305
let mut input: ClaudePreToolUseInput = serde_json::from_str(&stdin()?)
297306
.map_err(|e| anyhow::anyhow!("Failed to parse input JSON: {}", e))?;
307+
298308
let dir = std::path::Path::new(&input.tool_input.file_path)
299309
.parent()
300310
.ok_or(anyhow!("Failed to get parent directory of file path"))?;
@@ -312,6 +322,14 @@ pub fn handle_pre_tool_call() -> anyhow::Result<ClaudeHookOutput> {
312322
let ctx = &mut CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
313323
let session_id = original_session_id(ctx, input.session_id.clone())?;
314324

325+
if should_exit_early(ctx, &input.session_id)? {
326+
return Ok(ClaudeHookOutput {
327+
do_continue: true,
328+
stop_reason: "Session running in GUI, skipping hook".to_string(),
329+
suppress_output: true,
330+
});
331+
}
332+
315333
file_lock::obtain(ctx, session_id, input.tool_input.file_path.clone())?;
316334

317335
Ok(ClaudeHookOutput {
@@ -356,6 +374,15 @@ pub fn handle_post_tool_call() -> anyhow::Result<ClaudeHookOutput> {
356374
input.tool_response.file_path = relative_file_path;
357375

358376
let ctx = &mut CommandContext::open(&project, AppSettings::load_from_default_path_creating()?)?;
377+
378+
if should_exit_early(ctx, &input.session_id)? {
379+
return Ok(ClaudeHookOutput {
380+
do_continue: true,
381+
stop_reason: "Session running in GUI, skipping hook".to_string(),
382+
suppress_output: true,
383+
});
384+
}
385+
359386
let session_id = original_session_id(ctx, input.session_id.clone())?;
360387

361388
let defer = ClearLocksGuard {
@@ -542,3 +569,18 @@ fn list_stacks(ctx: &CommandContext) -> anyhow::Result<Vec<StackEntry>> {
542569
but_workspace::stacks(ctx, &ctx.project().gb_dir(), &repo, StacksFilter::default())
543570
}
544571
}
572+
573+
/// Returns true if the session has `is_gui` set to true, and `GUTBUTLER_IN_GUI` is unset
574+
fn should_exit_early(ctx: &mut CommandContext, session_id: &str) -> anyhow::Result<bool> {
575+
let in_gui = std::env::var("GITBUTLER_IN_GUI").unwrap_or("0".into()) == "1";
576+
if in_gui {
577+
return Ok(false);
578+
}
579+
580+
let session_uuid = Uuid::parse_str(session_id)?;
581+
if let Ok(Some(session)) = crate::db::get_session_by_current_id(ctx, session_uuid) {
582+
return Ok(session.in_gui);
583+
}
584+
585+
Ok(false)
586+
}

crates/but-claude/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ pub struct ClaudeSession {
2323
created_at: chrono::NaiveDateTime,
2424
/// The timestamp when the session was last updated.
2525
updated_at: chrono::NaiveDateTime,
26+
/// Whether this session is used by the GUI.
27+
in_gui: bool,
2628
}
2729

2830
/// Represents a message in a Claude session, referencing the stable session ID.

crates/but-db/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ anyhow = "1.0.98"
2323
diesel_migrations = { version = "2.0.0", features = ["sqlite"] }
2424
chrono = { version = "0.4.41", features = ["serde"] }
2525
# other things
26-
tokio = { workspace = true, features = ["rt-multi-thread", "parking_lot", "time", "sync"] }
26+
tokio = { workspace = true, features = ["rt-multi-thread", "parking_lot", "time", "sync", "macros"] }
2727
tracing.workspace = true
2828

2929
[dev-dependencies]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Remove inGui column from claude_sessions table
2+
ALTER TABLE claude_sessions DROP COLUMN in_gui;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Add inGui column to claude_sessions table
2+
ALTER TABLE claude_sessions ADD COLUMN in_gui BOOLEAN NOT NULL DEFAULT FALSE;

crates/but-db/src/claude.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub struct ClaudeSession {
1818
pub current_id: String,
1919
pub created_at: chrono::NaiveDateTime,
2020
pub updated_at: chrono::NaiveDateTime,
21+
pub in_gui: bool,
2122
}
2223

2324
#[derive(
@@ -162,7 +163,11 @@ impl ClaudeSessionsHandle<'_> {
162163
Ok(())
163164
}
164165

165-
pub fn update(&mut self, id: &str, current_id: &str) -> Result<(), diesel::result::Error> {
166+
pub fn update_current_id(
167+
&mut self,
168+
id: &str,
169+
current_id: &str,
170+
) -> Result<(), diesel::result::Error> {
166171
diesel::update(claude_sessions.filter(crate::schema::claude_sessions::id.eq(id)))
167172
.set((
168173
crate::schema::claude_sessions::current_id.eq(current_id),
@@ -172,6 +177,16 @@ impl ClaudeSessionsHandle<'_> {
172177
Ok(())
173178
}
174179

180+
pub fn update_in_gui(&mut self, id: &str, in_gui: bool) -> Result<(), diesel::result::Error> {
181+
diesel::update(claude_sessions.filter(crate::schema::claude_sessions::id.eq(id)))
182+
.set((
183+
crate::schema::claude_sessions::in_gui.eq(in_gui),
184+
crate::schema::claude_sessions::updated_at.eq(chrono::Local::now().naive_local()),
185+
))
186+
.execute(&mut self.db.conn)?;
187+
Ok(())
188+
}
189+
175190
/// If you intend delete the messages AND the session, you should use `delete_session_and_messages` instead, which does it all in a single transaction.
176191
pub fn delete(&mut self, id: &str) -> Result<(), diesel::result::Error> {
177192
diesel::delete(claude_sessions.filter(crate::schema::claude_sessions::id.eq(id)))

crates/but-db/src/schema.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ diesel::table! {
6161
current_id -> Text,
6262
created_at -> Timestamp,
6363
updated_at -> Timestamp,
64+
in_gui -> Bool,
6465
}
6566
}
6667

0 commit comments

Comments
 (0)