From 74482375a9f2f9b318b63ddc4e267e3df026e7a2 Mon Sep 17 00:00:00 2001 From: Alex Netsch Date: Thu, 17 Jul 2025 15:22:31 +0100 Subject: [PATCH] Replace bugged hooks with kill when exit_plan_mode is called (#244) --- backend/src/executors/claude.rs | 83 +++++++++++++-------------------- 1 file changed, 32 insertions(+), 51 deletions(-) diff --git a/backend/src/executors/claude.rs b/backend/src/executors/claude.rs index c0803578..dc149c07 100644 --- a/backend/src/executors/claude.rs +++ b/backend/src/executors/claude.rs @@ -2,7 +2,7 @@ use std::path::Path; use async_trait::async_trait; use command_group::{AsyncCommandGroup, AsyncGroupChild}; -use tokio::{fs, process::Command}; +use tokio::process::Command; use uuid::Uuid; use crate::{ @@ -14,47 +14,29 @@ use crate::{ utils::shell::get_shell_command, }; -/// Create Claude settings file with PostToolUse hook for exit_plan_mode -async fn create_claude_settings_file(worktree_path: &str) -> Result<(), ExecutorError> { - let claude_dir = Path::new(worktree_path).join(".claude"); - let settings_file = claude_dir.join("settings.local.json"); +fn create_watchkill_script(command: &str) -> String { + let claude_plan_stop_indicator = + "Claude requested permissions to use exit_plan_mode, but you haven't granted it yet"; + format!( + r#"#!/usr/bin/env bash +set -euo pipefail - // Create .claude directory if it doesn't exist - fs::create_dir_all(&claude_dir).await.map_err(|e| { - tracing::warn!("Failed to create .claude directory: {}", e); - ExecutorError::GitError(format!("Failed to create .claude directory: {}", e)) - })?; +word="{}" +command="{}" - // Create settings content with PreToolUse hook to auto-approve exit_plan_mode - let settings_content = r#"{ - "hooks": { - "PreToolUse": [ - { - "matcher": "exit_plan_mode", - "hooks": [ - { - "type": "command", - "command": "echo '{\"decision\": \"approve\", \"reason\": \"Auto-approving exit_plan_mode tool\", \"continue\": false, \"stopReason\": \"Plan presented - waiting for user approval before continuing\"}'" - } - ] - } - ] - } -}"#; +exit_code=0 +while IFS= read -r line; do + printf '%s\n' "$line" + if [[ $line == *"$word"* ]]; then + exit 0 + fi +done < <($command <&0 2>&1) - // Write settings file - fs::write(&settings_file, settings_content) - .await - .map_err(|e| { - tracing::warn!("Failed to write Claude settings file: {}", e); - ExecutorError::GitError(format!("Failed to write Claude settings file: {}", e)) - })?; - - tracing::info!( - "Created Claude settings file at: {}", - settings_file.display() - ); - Ok(()) +exit_code=${{PIPESTATUS[0]}} +exit "$exit_code" +"#, + claude_plan_stop_indicator, command + ) } /// An executor that uses Claude CLI to process tasks @@ -79,9 +61,11 @@ impl ClaudeExecutor { } pub fn new_plan_mode() -> Self { + let command = "npx -y @anthropic-ai/claude-code@latest -p --permission-mode=plan --verbose --output-format=stream-json"; + let script = create_watchkill_script(command); Self { executor_type: "ClaudePlan".to_string(), - command: "npx -y @anthropic-ai/claude-code@latest -p --permission-mode=plan --verbose --output-format=stream-json".to_string() + command: script, } } @@ -114,11 +98,18 @@ impl ClaudeFollowupExecutor { } pub fn new_plan_mode(session_id: String, prompt: String) -> Self { + let command = format!( + "npx -y @anthropic-ai/claude-code@latest -p --permission-mode=plan --verbose --output-format=stream-json --resume={}", + session_id + ); + + let script = create_watchkill_script(&command); + Self { session_id, prompt, executor_type: "ClaudePlan".to_string(), - command_base: "npx -y @anthropic-ai/claude-code@latest -p --permission-mode=plan --verbose --output-format=stream-json".to_string() + command_base: script, } } @@ -151,11 +142,6 @@ impl Executor for ClaudeExecutor { .await? .ok_or(ExecutorError::TaskNotFound)?; - // Create Claude settings file with PostToolUse hook for plan mode - if self.executor_type == "ClaudePlan" { - create_claude_settings_file(worktree_path).await?; - } - let prompt = if let Some(task_description) = task.description { format!( r#"project_id: {} @@ -689,11 +675,6 @@ impl Executor for ClaudeFollowupExecutor { _task_id: Uuid, worktree_path: &str, ) -> Result { - // Create Claude settings file with PostToolUse hook for plan mode - if self.executor_type == "ClaudePlan" { - create_claude_settings_file(worktree_path).await?; - } - // Use shell command for cross-platform compatibility let (shell_cmd, shell_arg) = get_shell_command(); // Pass prompt via stdin instead of command line to avoid shell escaping issues