spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / plugins / accept-with-feedback / hooks / accept_with_feedback.py
152 lines · 4.5 KBRaw
1#!/usr/bin/env python3
2"""
3Accept with Feedback Hook for Claude Code
4
5This hook intercepts permission requests and allows users to provide feedback
6when accepting operations. The feedback is passed to Claude as a system message,
7giving users a way to provide guidance while still approving the operation.
8
9Usage:
101. Set pending feedback: /accept-feedback "your guidance here"
112. When a permission prompt appears, if pending feedback exists:
12 - The operation is automatically approved
13 - Your feedback is sent to Claude as guidance
143. Or configure always-on feedback rules in .claude/accept-feedback.json
15"""
16
17import json
18import os
19import sys
20from datetime import datetime
21from pathlib import Path
22
23
24def get_config_paths():
25 """Get paths for config and state files."""
26 claude_dir = Path.home() / ".claude"
27 project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", "."))
28
29 return {
30 "user_config": claude_dir / "accept-feedback.json",
31 "project_config": project_dir / ".claude" / "accept-feedback.json",
32 "pending_feedback": claude_dir / "pending-accept-feedback.json",
33 }
34
35
36def load_json_file(path):
37 """Load JSON from file, return empty dict if not found."""
38 try:
39 if path.exists():
40 with open(path, "r") as f:
41 return json.load(f)
42 except (json.JSONDecodeError, IOError):
43 pass
44 return {}
45
46
47def get_pending_feedback(session_id):
48 """Get pending feedback for this session and clear it."""
49 paths = get_config_paths()
50 pending_file = paths["pending_feedback"]
51
52 pending = load_json_file(pending_file)
53
54 # Check for session-specific pending feedback
55 if session_id in pending:
56 feedback = pending[session_id]
57 # Clear the pending feedback after use
58 del pending[session_id]
59 try:
60 if pending:
61 with open(pending_file, "w") as f:
62 json.dump(pending, f)
63 else:
64 pending_file.unlink(missing_ok=True)
65 except IOError:
66 pass
67 return feedback.get("message"), feedback.get("one_time", True)
68
69 return None, True
70
71
72def get_configured_feedback(tool_name, tool_input):
73 """Get feedback configured for this tool type."""
74 paths = get_config_paths()
75
76 # Load both user and project configs
77 user_config = load_json_file(paths["user_config"])
78 project_config = load_json_file(paths["project_config"])
79
80 # Project config takes precedence
81 config = {**user_config, **project_config}
82
83 rules = config.get("rules", [])
84
85 for rule in rules:
86 # Check if rule matches this tool
87 matcher = rule.get("matcher", "*")
88 if matcher == "*" or tool_name in matcher.split("|"):
89 # Check conditions if specified
90 conditions = rule.get("conditions", {})
91 matches = True
92
93 for key, pattern in conditions.items():
94 value = str(tool_input.get(key, ""))
95 if pattern not in value:
96 matches = False
97 break
98
99 if matches:
100 return rule.get("feedback")
101
102 return None
103
104
105def main():
106 """Main hook function."""
107 # Read input from stdin
108 try:
109 raw_input = sys.stdin.read()
110 input_data = json.loads(raw_input)
111 except json.JSONDecodeError:
112 # Can't parse input, don't interfere
113 sys.exit(0)
114
115 session_id = input_data.get("session_id", "default")
116 tool_name = input_data.get("tool_name", "")
117 tool_input = input_data.get("tool_input", {})
118
119 # Check for pending feedback first (from /accept-feedback command)
120 pending_message, _ = get_pending_feedback(session_id)
121
122 if pending_message:
123 # We have pending feedback - approve with the feedback as system message
124 result = {
125 "hookSpecificOutput": {
126 "permissionDecision": "allow"
127 },
128 "systemMessage": f"[User Feedback on Approval]: {pending_message}"
129 }
130 print(json.dumps(result))
131 sys.exit(0)
132
133 # Check for configured feedback rules
134 configured_feedback = get_configured_feedback(tool_name, tool_input)
135
136 if configured_feedback:
137 # We have configured feedback for this tool type
138 result = {
139 "hookSpecificOutput": {
140 "permissionDecision": "allow"
141 },
142 "systemMessage": f"[User Guidance]: {configured_feedback}"
143 }
144 print(json.dumps(result))
145 sys.exit(0)
146
147 # No feedback configured - don't interfere with the normal permission flow
148 sys.exit(0)
149
150
151if __name__ == "__main__":
152 main()