aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/detect/manifest/tests.rs79
-rw-r--r--src/detect/manifests/claude.toml12
2 files changed, 81 insertions, 10 deletions
diff --git a/src/detect/manifest/tests.rs b/src/detect/manifest/tests.rs
index 8eb3b612..63345636 100644
--- a/src/detect/manifest/tests.rs
+++ b/src/detect/manifest/tests.rs
@@ -660,6 +660,85 @@ fn osc_explain(
// --- Claude OSC rules ---
#[test]
+fn claude_idle_prompt_with_background_shell_is_idle() {
+ // Captured from Claude Code 2.1.251 after its foreground turn ended while
+ // a long-lived background shell remained active (issue #3414).
+ let screen = concat!(
+ "✻ Sautéed for 10s · 1 shell still running\n\n",
+ "──────────────────────────────────────────────────────── WINDOWS ─\n",
+ "❯\n",
+ "────────────────────────────────────────────────────────────────\n",
+ " ⏵⏵ auto mode on · 1 shell · ← for agents /rc\n",
+ );
+ let result = osc_explain(Agent::Claude, screen, "", "");
+
+ assert_eq!(result.state, AgentState::Idle);
+ assert_eq!(
+ result.matched_rule.as_ref().map(|rule| rule.id.as_str()),
+ Some("live_prompt_box")
+ );
+ assert!(result.visible_idle);
+ assert!(!result.visible_working);
+}
+
+#[test]
+fn claude_background_shell_without_foreground_evidence_is_idle_fallback() {
+ let result = osc_explain(
+ Agent::Claude,
+ " ⏵⏵ auto mode on · 1 shell · ← for agents\n",
+ "",
+ "",
+ );
+
+ assert_eq!(result.state, AgentState::Idle);
+ assert_eq!(result.matched_rule, None);
+ assert_eq!(
+ result.fallback_reason.as_deref(),
+ Some(DEFAULT_KNOWN_AGENT_IDLE_FALLBACK)
+ );
+ assert!(!result.visible_working);
+}
+
+#[test]
+fn claude_live_turn_with_background_shell_remains_working() {
+ let screen = concat!(
+ "────────────────────────────────────────────────────────────────\n",
+ "❯\n",
+ "────────────────────────────────────────────────────────────────\n",
+ " ⏵⏵ auto mode on · 1 shell · esc to interrupt\n",
+ );
+ let result = osc_explain(Agent::Claude, screen, "", "");
+
+ assert_eq!(result.state, AgentState::Working);
+ assert_eq!(
+ result.matched_rule.as_ref().map(|rule| rule.id.as_str()),
+ Some("live_turn_working")
+ );
+ assert!(result.visible_working);
+}
+
+#[test]
+fn claude_blocker_with_background_shell_remains_blocked() {
+ let screen = concat!(
+ "do you want to proceed?\n",
+ "bash command: rm -rf /tmp/test\n",
+ "❯ 1. Yes\n",
+ " 2. No\n\n",
+ "Esc to cancel · Tab to amend · ctrl+e to explain\n",
+ " ⏵⏵ auto mode on · 1 shell · ← for agents\n",
+ );
+ let result = osc_explain(Agent::Claude, screen, "", "");
+
+ assert_eq!(result.state, AgentState::Blocked);
+ assert_eq!(
+ result.matched_rule.as_ref().map(|rule| rule.id.as_str()),
+ Some("bash_permission_prompt")
+ );
+ assert!(result.visible_blocker);
+ assert!(!result.visible_working);
+}
+
+#[test]
fn claude_osc_title_braille_prefix_is_working() {
// "⠂" is U+2802, in the braille block U+2800-U+28FF
let result = osc_explain(Agent::Claude, "", "⠂ project", "");
diff --git a/src/detect/manifests/claude.toml b/src/detect/manifests/claude.toml
index 55adacd6..2a26a334 100644
--- a/src/detect/manifests/claude.toml
+++ b/src/detect/manifests/claude.toml
@@ -1,7 +1,7 @@
id = "claude"
-version = "2026.08.29.1"
+version = "2026.08.31.1"
min_engine_version = 2
-updated_at = "2026-08-29T00:00:00Z"
+updated_at = "2026-08-31T00:00:00Z"
aliases = ["claude-code"]
[[rules]]
@@ -25,14 +25,6 @@ any = [
]
[[rules]]
-id = "background_shell_working"
-state = "working"
-priority = 965
-region = "bottom_non_empty_lines(5)"
-visible_working = true
-line_regex = ['^\s*[⏸⏵].*·\s+[1-9]\d*\s+shells?\s+(?:·|$)']
-
-[[rules]]
id = "background_agents_working"
state = "working"
priority = 965