@@ -504,9 +504,9 @@ fn classify_claude_composer(plain: &str, expected: &str) -> ComposerState {
504504 } ;
505505 let exact = logical_inputs. iter ( ) . any ( |input| input == expected) ;
506506 let placeholder = logical_inputs. len ( ) == 1 && is_claude_idle_placeholder ( & logical_inputs[ 0 ] ) ;
507- let idle_footer = footer . contains ( "⏵⏵" )
508- && footer . contains ( "shift+tab to cycle" )
509- && footer. contains ( "permissions on" ) ;
507+ // The keybinding hint is runtime-composed and may be absent or remapped. The mode marker and
508+ // permission state are the stable safety signals; active/modal/changed states remain fail-closed.
509+ let idle_footer = footer . contains ( "⏵⏵" ) && footer. contains ( "permissions on" ) ;
510510 let blocked = interaction_blocked ( plain) ;
511511 if exact {
512512 if idle_footer && !blocked {
@@ -1426,6 +1426,10 @@ mod tests {
14261426 )
14271427 }
14281428
1429+ fn idle_claude_screen_without_hint ( ) -> String {
1430+ idle_claude_screen ( ) . replace ( " (shift+tab to cycle)" , "" )
1431+ }
1432+
14291433 fn staged_claude_screen ( text : & str ) -> String {
14301434 assert ! ( text. is_ascii( ) ) ;
14311435 let rule = claude_rule ( ) ;
@@ -1469,6 +1473,10 @@ mod tests {
14691473 classify_composer( & idle_claude_screen( ) , expected) ,
14701474 ComposerState :: EmptySafe
14711475 ) ;
1476+ assert_eq ! (
1477+ classify_composer( & idle_claude_screen_without_hint( ) , expected) ,
1478+ ComposerState :: EmptySafe
1479+ ) ;
14721480 assert_eq ! (
14731481 classify_composer( & staged_claude_screen( expected) , expected) ,
14741482 ComposerState :: ExactSafe
0 commit comments