diff options
| author | caner-akca <94343893+caner-akca@users.noreply.github.com> | 2026-08-30 01:18:16 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-30 01:18:16 +0300 |
| commit | f807b697353cfa00aa912c7cde4830e863001cf5 (patch) | |
| tree | 39dd05f851a3d3eef07d289f281a2babcfd32868 /src/detect | |
| parent | c2637dc182ddc5425108824d5ed15d24ce38c4e3 (diff) | |
fix: detect claude mcp elicitation dialogs as blocked (#3383)
refs #3283
Diffstat (limited to 'src/detect')
| -rw-r--r-- | src/detect/manifest/tests.rs | 24 | ||||
| -rw-r--r-- | src/detect/manifests/claude.toml | 23 |
2 files changed, 45 insertions, 2 deletions
diff --git a/src/detect/manifest/tests.rs b/src/detect/manifest/tests.rs index 508b9b7e..8eb3b612 100644 --- a/src/detect/manifest/tests.rs +++ b/src/detect/manifest/tests.rs @@ -747,6 +747,30 @@ fn claude_blocker_screen_outranks_osc_idle_title() { } #[test] +fn claude_mcp_elicitation_is_blocked() { + // Regression for issue #3283: an MCP elicitation dialog has Accept/Decline + // controls and an "Esc to cancel" footer but no Enter hint, so no blocked + // rule matched and the static OSC title reported idle. + // Live capture uses curly quotes around the server name; the issue report + // transcribed straight quotes. Both must classify as blocked. + for screen in [ + "MCP server \u{201c}my-server\u{201d} requests your input\n\nGrant temporary access to the demo gateway for 15 minutes?\n\n\u{276f} Accept Decline\n\nEsc to cancel \u{b7} \u{2191}/\u{2193} to navigate\n", + "MCP server \"my-server\" requests your input\n\nserver-supplied message\n\n\u{276f} Accept Decline\n\nEsc to cancel \u{b7} \u{2191}/\u{2193} to navigate\n", + ] { + let result = with_manifest_dirs("claude-mcp-elicitation", || { + osc_explain(Agent::Claude, screen, "\u{2733} Claude Code", "") + }); + assert_eq!(result.state, AgentState::Blocked, "{result:#?}"); + assert!(result.visible_blocker, "{result:#?}"); + assert_eq!( + result.matched_rule.as_ref().map(|r| r.id.as_str()), + Some("mcp_elicitation_prompt"), + "{result:#?}" + ); + } +} + +#[test] fn claude_empty_osc_empty_screen_is_idle_fallback() { // No OSC data, no matching screen rule → fallback idle (unchanged V3 behavior) let result = osc_explain(Agent::Claude, "", "", ""); diff --git a/src/detect/manifests/claude.toml b/src/detect/manifests/claude.toml index 696a1978..55adacd6 100644 --- a/src/detect/manifests/claude.toml +++ b/src/detect/manifests/claude.toml @@ -1,7 +1,7 @@ id = "claude" -version = "2026.08.21.1" +version = "2026.08.29.1" min_engine_version = 2 -updated_at = "2026-08-21T00:00:00Z" +updated_at = "2026-08-29T00:00:00Z" aliases = ["claude-code"] [[rules]] @@ -111,6 +111,25 @@ visible_blocker = true contains = ["run a dynamic workflow?", "esc to cancel"] [[rules]] +id = "mcp_elicitation_prompt" +state = "blocked" +priority = 980 +region = "whole_recent" +visible_blocker = true +# MCP elicitation dialogs (elicitation/create) show Accept/Decline controls +# with an "Esc to cancel" footer but no Enter hint, so live_blocked_form +# cannot see them (issue #3283). Gate on the invariant header line, the +# Accept/Decline control line, and the cancel footer. +contains = ["esc to cancel"] +line_regex = ['(?i)^\s*MCP server ["\x{201C}].+["\x{201D}] requests your input\s*$'] +all = [ + { any = [ + { line_regex = ['^\s*\x{276F}?\s*Accept\b'] }, + { line_regex = ['^\s*\x{276F}?\s*Decline\b'] }, + ] }, +] + +[[rules]] id = "live_prompt_box" state = "idle" priority = 950 |
