Replace bugged hooks with kill when exit_plan_mode is called (#244)

This commit is contained in:
Alex Netsch
2025-07-17 15:22:31 +01:00
committed by GitHub
parent 25d97201c0
commit 74482375a9

View File

@@ -2,7 +2,7 @@ use std::path::Path;
use async_trait::async_trait; use async_trait::async_trait;
use command_group::{AsyncCommandGroup, AsyncGroupChild}; use command_group::{AsyncCommandGroup, AsyncGroupChild};
use tokio::{fs, process::Command}; use tokio::process::Command;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
@@ -14,47 +14,29 @@ use crate::{
utils::shell::get_shell_command, utils::shell::get_shell_command,
}; };
/// Create Claude settings file with PostToolUse hook for exit_plan_mode fn create_watchkill_script(command: &str) -> String {
async fn create_claude_settings_file(worktree_path: &str) -> Result<(), ExecutorError> { let claude_plan_stop_indicator =
let claude_dir = Path::new(worktree_path).join(".claude"); "Claude requested permissions to use exit_plan_mode, but you haven't granted it yet";
let settings_file = claude_dir.join("settings.local.json"); format!(
r#"#!/usr/bin/env bash
set -euo pipefail
// Create .claude directory if it doesn't exist word="{}"
fs::create_dir_all(&claude_dir).await.map_err(|e| { command="{}"
tracing::warn!("Failed to create .claude directory: {}", e);
ExecutorError::GitError(format!("Failed to create .claude directory: {}", e))
})?;
// Create settings content with PreToolUse hook to auto-approve exit_plan_mode exit_code=0
let settings_content = r#"{ while IFS= read -r line; do
"hooks": { printf '%s\n' "$line"
"PreToolUse": [ if [[ $line == *"$word"* ]]; then
{ exit 0
"matcher": "exit_plan_mode", fi
"hooks": [ done < <($command <&0 2>&1)
{
"type": "command",
"command": "echo '{\"decision\": \"approve\", \"reason\": \"Auto-approving exit_plan_mode tool\", \"continue\": false, \"stopReason\": \"Plan presented - waiting for user approval before continuing\"}'"
}
]
}
]
}
}"#;
// Write settings file exit_code=${{PIPESTATUS[0]}}
fs::write(&settings_file, settings_content) exit "$exit_code"
.await "#,
.map_err(|e| { claude_plan_stop_indicator, command
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(())
} }
/// An executor that uses Claude CLI to process tasks /// An executor that uses Claude CLI to process tasks
@@ -79,9 +61,11 @@ impl ClaudeExecutor {
} }
pub fn new_plan_mode() -> Self { 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 { Self {
executor_type: "ClaudePlan".to_string(), 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 { 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 { Self {
session_id, session_id,
prompt, prompt,
executor_type: "ClaudePlan".to_string(), 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? .await?
.ok_or(ExecutorError::TaskNotFound)?; .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 { let prompt = if let Some(task_description) = task.description {
format!( format!(
r#"project_id: {} r#"project_id: {}
@@ -689,11 +675,6 @@ impl Executor for ClaudeFollowupExecutor {
_task_id: Uuid, _task_id: Uuid,
worktree_path: &str, worktree_path: &str,
) -> Result<AsyncGroupChild, ExecutorError> { ) -> Result<AsyncGroupChild, ExecutorError> {
// 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 // Use shell command for cross-platform compatibility
let (shell_cmd, shell_arg) = get_shell_command(); let (shell_cmd, shell_arg) = get_shell_command();
// Pass prompt via stdin instead of command line to avoid shell escaping issues // Pass prompt via stdin instead of command line to avoid shell escaping issues