use crate::error::Error;
pub(crate) const PERMISSION_PATTERNS: &[&str] = &[
"Allow",
"allow this action",
"approve",
"permission",
"Do you want to",
"tool requires approval",
"wants to use",
"Press Enter",
"y/n",
"Y/n",
"(yes/no)",
];
pub(crate) struct FailureDetails {
pub(crate) failed_command: Option<String>,
pub(crate) exit_code: Option<i32>,
pub(crate) stderr: Option<String>,
}
pub(crate) fn extract_failure_details(err: &Error) -> FailureDetails {
match err {
Error::Wrapper(claude_wrapper::Error::CommandFailed {
command,
exit_code,
stderr,
..
}) => FailureDetails {
failed_command: Some(command.clone()),
exit_code: Some(*exit_code),
stderr: if stderr.is_empty() {
None
} else {
Some(stderr.clone())
},
},
_ => FailureDetails {
failed_command: None,
exit_code: None,
stderr: None,
},
}
}
pub(crate) fn detect_permission_prompt(
err: &claude_wrapper::Error,
slot_id: &str,
) -> Option<Error> {
let stderr = match err {
claude_wrapper::Error::CommandFailed { stderr, .. } => stderr,
_ => return None,
};
if stderr.is_empty() {
return None;
}
for pattern in PERMISSION_PATTERNS {
if stderr.contains(pattern) {
let tool_name = extract_tool_name(stderr);
tracing::warn!(
slot_id,
tool = %tool_name,
"permission prompt detected in slot stderr"
);
return Some(Error::PermissionPromptDetected {
tool_name,
stderr: stderr.clone(),
slot_id: slot_id.to_string(),
});
}
}
None
}
pub(crate) fn extract_tool_name(stderr: &str) -> String {
for line in stderr.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("Allow ")
&& let Some(tool) = rest.split_whitespace().next()
{
return tool.trim_end_matches('?').to_string();
}
if let Some(idx) = trimmed.find("wants to use ") {
let after = &trimmed[idx + "wants to use ".len()..];
if let Some(tool) = after.split_whitespace().next() {
return tool.trim_end_matches(['.', '?', ',']).to_string();
}
}
}
"unknown".to_string()
}