test(errors): add #3020 test extensions — Plan-mode denial passes through verbatim, bare/model denials get the suffix; Model-Not-Exist + OpenAI-style rejections annotated; conflict error includes elapsed time; tighten mode-word predicate so 'model' no longer matches

Co-Authored-By: Claude <noreply@anthropic.com>
https://claude.ai/code/session_018zaP8vUfTAsrE38L6h6fw5
This commit is contained in:
Claude
2026-06-11 02:30:30 +00:00
parent c98b7ea42c
commit 5fb41cc209
3 changed files with 61 additions and 2 deletions
+11 -2
View File
@@ -99,6 +99,15 @@ pub(super) fn caller_allowed_for_tool(
requested == "direct"
}
/// Whole-word check for "mode"/"modes" — a plain `contains("mode")` also
/// matched "model", letting provider model errors skip the actionable-hint
/// suffix (#3020).
fn mentions_mode_word(lower: &str) -> bool {
lower
.split(|ch: char| !ch.is_ascii_alphanumeric())
.any(|word| word == "mode" || word == "modes")
}
pub(super) fn format_tool_error(err: &ToolError, tool_name: &str) -> String {
match err {
ToolError::InvalidInput { message } => {
@@ -123,7 +132,7 @@ pub(super) fn format_tool_error(err: &ToolError, tool_name: &str) -> String {
// "switch to Agent, Goal, or YOLO mode" which confuses the model.
if lower.contains("current tool catalog")
|| lower.contains("did you mean:")
|| lower.contains("mode")
|| mentions_mode_word(&lower)
|| lower.contains("allow_shell")
|| lower.contains("feature flag")
{
@@ -137,7 +146,7 @@ pub(super) fn format_tool_error(err: &ToolError, tool_name: &str) -> String {
ToolError::PermissionDenied { message } => {
let lower = message.to_ascii_lowercase();
// #3020: Pass through messages that already name the denial cause.
if lower.contains("mode")
if mentions_mode_word(&lower)
|| lower.contains("allow_shell")
|| lower.contains("denied by user")
{
+27
View File
@@ -486,6 +486,33 @@ fn tool_error_messages_include_actionable_hints() {
let timeout = ToolError::Timeout { seconds: 5 };
let formatted = format_tool_error(&timeout, "exec_shell");
assert!(formatted.contains("timed out"));
// #3020: Plan-mode denials already explain the fix — pass through
// verbatim, with no conflicting "Adjust approval mode" suffix.
let plan_denied = ToolError::permission_denied(
"'exec_shell' is not available in Plan mode — switch to Agent, Goal, or YOLO mode to run commands and code.",
);
let formatted = format_tool_error(&plan_denied, "exec_shell");
assert_eq!(
formatted,
"'exec_shell' is not available in Plan mode — switch to Agent, Goal, or YOLO mode to run commands and code."
);
// Bare denials still get the actionable suffix.
let bare_denied = ToolError::permission_denied("nope");
let formatted = format_tool_error(&bare_denied, "exec_shell");
assert!(
formatted.contains("Adjust approval mode or request permission"),
"{formatted}"
);
// "model" must not satisfy the "mode" pass-through check.
let model_denied = ToolError::permission_denied("requested model is not allowed");
let formatted = format_tool_error(&model_denied, "agent_open");
assert!(
formatted.contains("Adjust approval mode or request permission"),
"{formatted}"
);
}
#[test]
+23
View File
@@ -1489,6 +1489,12 @@ async fn spawn_duplicate_session_name_error_names_conflicting_agent() {
msg.contains("running"),
"includes the conflicting status: {msg}"
);
// #3020: elapsed time lets the parent distinguish a live worker from a
// stale earlier spawn.
assert!(
msg.contains("started ") && msg.contains(" ago"),
"includes elapsed time since spawn: {msg}"
);
}
#[tokio::test]
@@ -2074,6 +2080,23 @@ fn annotate_child_model_error_adds_actionable_hint() {
let unrelated = annotate_child_model_error("connection reset by peer", "kimi-k2");
assert_eq!(unrelated, "connection reset by peer");
// #3020: provider rejections that classify as Internal (not
// Authorization/State) still get the hint via raw-text matching.
let not_exist = annotate_child_model_error("Model Not Exist", "kimi-k2");
assert!(
not_exist.contains("retry agent_open"),
"DeepSeek-style rejection gets the hint: {not_exist}"
);
let openai_style = annotate_child_model_error(
"The model `gpt-5.5-nano` does not exist or you do not have access to it.",
"gpt-5.5-nano",
);
assert!(
openai_style.contains("retry agent_open"),
"OpenAI-style rejection gets the hint: {openai_style}"
);
}
#[test]