Replace bugged hooks with kill when exit_plan_mode is called (#244)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user