@@ -311,8 +311,15 @@ def _configure_guard_parser(guard_parser: argparse.ArgumentParser) -> None:
311311 policy_parser .add_argument ("--json" , action = "store_true" )
312312 policy_parser .set_defaults (policy_action = action )
313313
314- policies_parser = guard_subparsers .add_parser ("policies" , help = "List stored Guard policy decisions" )
314+ policies_parser = guard_subparsers .add_parser ("policies" , help = "List or clear stored Guard policy decisions" )
315+ policies_parser .add_argument ("policies_command" , nargs = "?" , choices = ("clear" ,))
315316 policies_parser .add_argument ("--harness" )
317+ policies_parser .add_argument ("--source" )
318+ policies_parser .add_argument (
319+ "--all" ,
320+ action = "store_true" ,
321+ help = "Clear decisions across every harness; cannot be combined with --harness" ,
322+ )
316323 _add_guard_common_args (policies_parser )
317324 policies_parser .add_argument ("--json" , action = "store_true" )
318325
@@ -775,6 +782,46 @@ def interactive_resolver(detection, payload):
775782 return 0
776783
777784 if args .guard_command == "policies" :
785+ if getattr (args , "policies_command" , None ) == "clear" :
786+ harness = getattr (args , "harness" , None )
787+ clear_all = bool (getattr (args , "all" , False ))
788+ if clear_all and harness is not None :
789+ _emit (
790+ "policies" ,
791+ {
792+ "error" : "Choose either --all or --harness <name> when clearing Guard policy decisions." ,
793+ "cleared" : 0 ,
794+ "harness" : harness ,
795+ "source" : getattr (args , "source" , None ),
796+ },
797+ getattr (args , "json" , False ),
798+ )
799+ return 2
800+ if not clear_all and harness is None :
801+ _emit (
802+ "policies" ,
803+ {
804+ "error" : "Choose --harness <name> or --all when clearing Guard policy decisions." ,
805+ "cleared" : 0 ,
806+ },
807+ getattr (args , "json" , False ),
808+ )
809+ return 2
810+ cleared = store .clear_policy_decisions (
811+ None if clear_all else harness ,
812+ getattr (args , "source" , None ),
813+ )
814+ _emit (
815+ "policies" ,
816+ {
817+ "generated_at" : _now (),
818+ "cleared" : cleared ,
819+ "harness" : None if clear_all else harness ,
820+ "source" : getattr (args , "source" , None ),
821+ },
822+ getattr (args , "json" , False ),
823+ )
824+ return 0
778825 policy_items = store .list_policy_decisions (getattr (args , "harness" , None ))
779826 items = _filter_policy_items (policy_items , active_only = True )
780827 _emit ("policies" , {"generated_at" : _now (), "items" : items }, getattr (args , "json" , False ))
@@ -1233,10 +1280,13 @@ def interactive_resolver(detection, payload):
12331280 )
12341281 return 0
12351282 if _canonical_harness_name (args .harness ) == "claude-code" and _hook_event_name (payload ) == "Stop" :
1236- denied = _persist_claude_pending_permission_denials (store , payload )
1283+ discarded = _discard_claude_pending_permissions (store , payload )
12371284 store .add_event (
12381285 "claude/turn_stop" ,
1239- {"session_id" : payload .get ("session_id" ), "saved_denials" : denied },
1286+ {
1287+ "session_id" : payload .get ("session_id" ),
1288+ "discarded_pending_permissions" : discarded ,
1289+ },
12401290 _now (),
12411291 )
12421292 return 0
@@ -1371,6 +1421,14 @@ def interactive_resolver(detection, payload):
13711421 artifact = runtime_artifact ,
13721422 artifact_hash = runtime_artifact_hash ,
13731423 )
1424+ if _should_allow_claude_user_prompt_submit_without_output (
1425+ args ,
1426+ event_name = event_name ,
1427+ policy_action = policy_action ,
1428+ artifact = runtime_artifact ,
1429+ output_stream = output_stream ,
1430+ ):
1431+ return 0
13741432 if _should_emit_copilot_hook_response (args ):
13751433 _emit_copilot_hook_response (
13761434 policy_action = policy_action ,
@@ -1724,6 +1782,23 @@ def _should_emit_prequeue_native_hook_response(
17241782 return output_stream is not None
17251783
17261784
1785+ def _should_allow_claude_user_prompt_submit_without_output (
1786+ args : argparse .Namespace ,
1787+ * ,
1788+ event_name : str ,
1789+ policy_action : str ,
1790+ artifact : GuardArtifact ,
1791+ output_stream : TextIO | None ,
1792+ ) -> bool :
1793+ return (
1794+ _canonical_harness_name (args .harness ) == "claude-code"
1795+ and event_name == "UserPromptSubmit"
1796+ and policy_action == "require-reapproval"
1797+ and not _prompt_requires_hard_block (artifact )
1798+ and (not getattr (args , "json" , False ) or output_stream is not None )
1799+ )
1800+
1801+
17271802def _emit_claude_permission_request_passthrough (* , output_stream : TextIO | None = None ) -> None :
17281803 if output_stream is not None :
17291804 output_stream .write ("" )
@@ -2044,6 +2119,27 @@ def _persist_claude_native_permission_for_runtime_artifact(
20442119 return True
20452120
20462121
2122+ def _discard_claude_pending_permissions (store : GuardStore , payload : dict [str , object ]) -> int :
2123+ session_id = _optional_string (payload .get ("session_id" ))
2124+ if session_id is None :
2125+ return 0
2126+ index_key = _claude_pending_permission_index_key (session_id )
2127+ try :
2128+ index_payload = store .get_sync_payload (index_key )
2129+ except (OSError , sqlite3 .Error ):
2130+ return 0
2131+ if not isinstance (index_payload , list ):
2132+ return 0
2133+ pending_keys = [str (item ) for item in index_payload ]
2134+ if not pending_keys :
2135+ return 0
2136+ try :
2137+ store .delete_sync_payloads ([* pending_keys , index_key ])
2138+ except (OSError , sqlite3 .Error ):
2139+ return 0
2140+ return len (pending_keys )
2141+
2142+
20472143def _persist_claude_pending_permission_denials (store : GuardStore , payload : dict [str , object ]) -> int :
20482144 session_id = _optional_string (payload .get ("session_id" ))
20492145 if session_id is None :
0 commit comments