mirror of
https://github.com/anthropics/claude-code.git
synced 2026-04-19 01:52:42 +00:00
Compare commits
1 Commits
claude/fix
...
fix/ralph-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ecb36849a |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(./scripts/comment-on-duplicates.sh:*)
|
||||
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*)
|
||||
description: Find duplicate GitHub issues
|
||||
---
|
||||
|
||||
@@ -11,13 +11,28 @@ To do this, follow these steps precisely:
|
||||
2. Use an agent to view a Github issue, and ask the agent to return a summary of the issue
|
||||
3. Then, launch 5 parallel agents to search Github for duplicates of this issue, using diverse keywords and search approaches, using the summary from #1
|
||||
4. Next, feed the results from #1 and #2 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
|
||||
5. Finally, use the comment script to post duplicates:
|
||||
```
|
||||
./scripts/comment-on-duplicates.sh --base-issue <issue-number> --potential-duplicates <dup1> <dup2> <dup3>
|
||||
```
|
||||
5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates)
|
||||
|
||||
Notes (be sure to tell this to your agents, too):
|
||||
|
||||
- Use `gh` to interact with Github, rather than web fetch
|
||||
- Do not use other tools, beyond `gh` and the comment script (eg. don't use other MCP servers, file edit, etc.)
|
||||
- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.)
|
||||
- Make a todo list first
|
||||
- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates):
|
||||
|
||||
---
|
||||
|
||||
Found 3 possible duplicate issues:
|
||||
|
||||
1. <link to issue>
|
||||
2. <link to issue>
|
||||
3. <link to issue>
|
||||
|
||||
This issue will be automatically closed as a duplicate in 3 days.
|
||||
|
||||
- If your issue is a duplicate, please close it and 👍 the existing issue instead
|
||||
- To prevent auto-closure, add a comment or 👎 this comment
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
---
|
||||
|
||||
120
CHANGELOG.md
120
CHANGELOG.md
@@ -1,125 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 2.1.0
|
||||
|
||||
- Added automatic skill hot-reload - skills created or modified in `~/.claude/skills` or `.claude/skills` are now immediately available without restarting the session
|
||||
- Added support for running skills and slash commands in a forked sub-agent context using `context: fork` in skill frontmatter
|
||||
- Added support for `agent` field in skills to specify agent type for execution
|
||||
- Added `language` setting to configure Claude's response language (e.g., language: "japanese")
|
||||
- Changed Shift+Enter to work out of the box in iTerm2, WezTerm, Ghostty, and Kitty without modifying terminal configs
|
||||
- Added `respectGitignore` support in `settings.json` for per-project control over @-mention file picker behavior
|
||||
- Added `IS_DEMO` environment variable to hide email and organization from the UI, useful for streaming or recording sessions
|
||||
- Fixed security issue where sensitive data (OAuth tokens, API keys, passwords) could be exposed in debug logs
|
||||
- Fixed files and skills not being properly discovered when resuming sessions with `-c` or `--resume`
|
||||
- Fixed pasted content being lost when replaying prompts from history using up arrow or Ctrl+R search
|
||||
- Fixed Esc key with queued prompts to only move them to input without canceling the running task
|
||||
- Reduced permission prompts for complex bash commands
|
||||
- Fixed command search to prioritize exact and prefix matches on command names over fuzzy matches in descriptions
|
||||
- Fixed PreToolUse hooks to allow `updatedInput` when returning `ask` permission decision, enabling hooks to act as middleware while still requesting user consent
|
||||
- Fixed plugin path resolution for file-based marketplace sources
|
||||
- Fixed LSP tool being incorrectly enabled when no LSP servers were configured
|
||||
- Fixed background tasks failing with "git repository not found" error for repositories with dots in their names
|
||||
- Fixed Claude in Chrome support for WSL environments
|
||||
- Fixed Windows native installer silently failing when executable creation fails
|
||||
- Improved CLI help output to display options and subcommands in alphabetical order for easier navigation
|
||||
- Added wildcard pattern matching for Bash tool permissions using `*` at any position in rules (e.g., `Bash(npm *)`, `Bash(* install)`, `Bash(git * main)`)
|
||||
- Added unified Ctrl+B backgrounding for both bash commands and agents - pressing Ctrl+B now backgrounds all running foreground tasks simultaneously
|
||||
- Added support for MCP `list_changed` notifications, allowing MCP servers to dynamically update their available tools, prompts, and resources without requiring reconnection
|
||||
- Added `/teleport` and `/remote-env` slash commands for claude.ai subscribers, allowing them to resume and configure remote sessions
|
||||
- Added support for disabling specific agents using `Task(AgentName)` syntax in settings.json permissions or the `--disallowedTools` CLI flag
|
||||
- Added hooks support to agent frontmatter, allowing agents to define PreToolUse, PostToolUse, and Stop hooks scoped to the agent's lifecycle
|
||||
- Added hooks support for skill and slash command frontmatter
|
||||
- Added new Vim motions: `;` and `,` to repeat f/F/t/T motions, `y` operator for yank with `yy`/`Y`, `p`/`P` for paste, text objects (`iw`, `aw`, `iW`, `aW`, `i"`, `a"`, `i'`, `a'`, `i(`, `a(`, `i[`, `a[`, `i{`, `a{`), `>>` and `<<` for indent/dedent, and `J` to join lines
|
||||
- Added `/plan` command shortcut to enable plan mode directly from the prompt
|
||||
- Added slash command autocomplete support when `/` appears anywhere in input, not just at the beginning
|
||||
- Added `--tools` flag support in interactive mode to restrict which built-in tools Claude can use during interactive sessions
|
||||
- Added `CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS` environment variable to override the default file read token limit
|
||||
- Added support for `once: true` config for hooks
|
||||
- Added support for YAML-style lists in frontmatter `allowed-tools` field for cleaner skill declarations
|
||||
- Added support for prompt and agent hook types from plugins (previously only command hooks were supported)
|
||||
- Added Cmd+V support for image paste in iTerm2 (maps to Ctrl+V)
|
||||
- Added left/right arrow key navigation for cycling through tabs in dialogs
|
||||
- Added real-time thinking block display in Ctrl+O transcript mode
|
||||
- Added filepath to full output in background bash task details dialog
|
||||
- Added Skills as a separate category in the context visualization
|
||||
- Fixed OAuth token refresh not triggering when server reports token expired but local expiration check disagrees
|
||||
- Fixed session persistence getting stuck after transient server errors by recovering from 409 conflicts when the entry was actually stored
|
||||
- Fixed session resume failures caused by orphaned tool results during concurrent tool execution
|
||||
- Fixed a race condition where stale OAuth tokens could be read from the keychain cache during concurrent token refresh attempts
|
||||
- Fixed AWS Bedrock subagents not inheriting EU/APAC cross-region inference model configuration, causing 403 errors when IAM permissions are scoped to specific regions
|
||||
- Fixed API context overflow when background tasks produce large output by truncating to 30K chars with file path reference
|
||||
- Fixed a hang when reading FIFO files by skipping symlink resolution for special file types
|
||||
- Fixed terminal keyboard mode not being reset on exit in Ghostty, iTerm2, Kitty, and WezTerm
|
||||
- Fixed Alt+B and Alt+F (word navigation) not working in iTerm2, Ghostty, Kitty, and WezTerm
|
||||
- Fixed `${CLAUDE_PLUGIN_ROOT}` not being substituted in plugin `allowed-tools` frontmatter, which caused tools to incorrectly require approval
|
||||
- Fixed files created by the Write tool using hardcoded 0o600 permissions instead of respecting the system umask
|
||||
- Fixed commands with `$()` command substitution failing with parse errors
|
||||
- Fixed multi-line bash commands with backslash continuations being incorrectly split and flagged for permissions
|
||||
- Fixed bash command prefix extraction to correctly identify subcommands after global options (e.g., `git -C /path log` now correctly matches `Bash(git log:*)` rules)
|
||||
- Fixed slash commands passed as CLI arguments (e.g., `claude /context`) not being executed properly
|
||||
- Fixed pressing Enter after Tab-completing a slash command selecting a different command instead of submitting the completed one
|
||||
- Fixed slash command argument hint flickering and inconsistent display when typing commands with arguments
|
||||
- Fixed Claude sometimes redundantly invoking the Skill tool when running slash commands directly
|
||||
- Fixed skill token estimates in `/context` to accurately reflect frontmatter-only loading
|
||||
- Fixed subagents sometimes not inheriting the parent's model by default
|
||||
- Fixed model picker showing incorrect selection for Bedrock/Vertex users using `--model haiku`
|
||||
- Fixed duplicate Bash commands appearing in permission request option labels
|
||||
- Fixed noisy output when background tasks complete - now shows clean completion message instead of raw output
|
||||
- Fixed background task completion notifications to appear proactively with bullet point
|
||||
- Fixed forked slash commands showing "AbortError" instead of "Interrupted" message when cancelled
|
||||
- Fixed cursor disappearing after dismissing permission dialogs
|
||||
- Fixed `/hooks` menu selecting wrong hook type when scrolling to a different option
|
||||
- Fixed images in queued prompts showing as "[object Object]" when pressing Esc to cancel
|
||||
- Fixed images being silently dropped when queueing messages while backgrounding a task
|
||||
- Fixed large pasted images failing with "Image was too large" error
|
||||
- Fixed extra blank lines in multiline prompts containing CJK characters (Japanese, Chinese, Korean)
|
||||
- Fixed ultrathink keyword highlighting being applied to wrong characters when user prompt text wraps to multiple lines
|
||||
- Fixed collapsed "Reading X files…" indicator incorrectly switching to past tense when thinking blocks appear mid-stream
|
||||
- Fixed Bash read commands (like `ls` and `cat`) not being counted in collapsed read/search groups, causing groups to incorrectly show "Read 0 files"
|
||||
- Fixed spinner token counter to properly accumulate tokens from subagents during execution
|
||||
- Fixed memory leak in git diff parsing where sliced strings retained large parent strings
|
||||
- Fixed race condition where LSP tool could return "no server available" during startup
|
||||
- Fixed feedback submission hanging indefinitely when network requests timeout
|
||||
- Fixed search mode in plugin discovery and log selector views exiting when pressing up arrow
|
||||
- Fixed hook success message showing trailing colon when hook has no output
|
||||
- Multiple optimizations to improve startup performance
|
||||
- Improved terminal rendering performance when using native installer or Bun, especially for text with emoji, ANSI codes, and Unicode characters
|
||||
- Improved performance when reading Jupyter notebooks with many cells
|
||||
- Improved reliability for piped input like `cat refactor.md | claude`
|
||||
- Improved reliability for AskQuestion tool
|
||||
- Improved sed in-place edit commands to render as file edits with diff preview
|
||||
- Improved Claude to automatically continue when response is cut off due to output token limit, instead of showing an error message
|
||||
- Improved compaction reliability
|
||||
- Improved subagents (Task tool) to continue working after permission denial, allowing them to try alternative approaches
|
||||
- Improved skills to show progress while executing, displaying tool uses as they happen
|
||||
- Improved skills from `/skills/` directories to be visible in the slash command menu by default (opt-out with `user-invocable: false` in frontmatter)
|
||||
- Improved skill suggestions to prioritize recently and frequently used skills
|
||||
- Improved spinner feedback when waiting for the first response token
|
||||
- Improved token count display in spinner to include tokens from background agents
|
||||
- Improved incremental output for async agents to give the main thread more control and visibility
|
||||
- Improved permission prompt UX with Tab hint moved to footer, cleaner Yes/No input labels with contextual placeholders
|
||||
- Improved the Claude in Chrome notification with shortened help text and persistent display until dismissed
|
||||
- Improved macOS screenshot paste reliability with TIFF format support
|
||||
- Improved `/stats` output
|
||||
- Updated Atlassian MCP integration to use a more reliable default configuration (streamable HTTP)
|
||||
- Changed "Interrupted" message color from red to grey for a less alarming appearance
|
||||
- Removed permission prompt when entering plan mode - users can now enter plan mode without approval
|
||||
- Removed underline styling from image reference links
|
||||
- [SDK] Changed minimum zod peer dependency to ^4.0.0
|
||||
- [VSCode] Added currently selected model name to the context menu
|
||||
- [VSCode] Added descriptive labels on auto-accept permission button (e.g., "Yes, allow npm for this project" instead of "Yes, and don't ask again")
|
||||
- [VSCode] Fixed paragraph breaks not rendering in markdown content
|
||||
- [VSCode] Fixed scrolling in the extension inadvertently scrolling the parent iframe
|
||||
- [Windows] Fixed issue with improper rendering
|
||||
|
||||
## 2.0.76
|
||||
|
||||
- Fixed issue with macOS code-sign warning when using Claude in Chrome integration
|
||||
|
||||
## 2.0.75
|
||||
|
||||
- Minor bugfixes
|
||||
|
||||
## 2.0.74
|
||||
|
||||
- Added LSP (Language Server Protocol) tool for code intelligence features like go-to-definition, find references, and hover documentation
|
||||
|
||||
@@ -141,39 +141,6 @@ class RuleEngine:
|
||||
patterns = matcher.split('|')
|
||||
return tool_name in patterns
|
||||
|
||||
def _resolve_symlink_path(self, file_path: str) -> str:
|
||||
"""Resolve symlinks in file path to get canonical path.
|
||||
|
||||
Security fix for CVE-2025-59829: Deny rules could be bypassed by creating
|
||||
a symlink to a restricted file. This method resolves the symlink to its
|
||||
target path so that deny rules are checked against the actual file.
|
||||
|
||||
Args:
|
||||
file_path: The file path that may contain symlinks
|
||||
|
||||
Returns:
|
||||
The canonical path with symlinks resolved, or original path if
|
||||
resolution fails (e.g., file doesn't exist yet)
|
||||
"""
|
||||
import os
|
||||
|
||||
if not file_path:
|
||||
return file_path
|
||||
|
||||
try:
|
||||
# Expand user home directory first
|
||||
expanded_path = os.path.expanduser(file_path)
|
||||
|
||||
# Use realpath to resolve all symlinks and get canonical path
|
||||
# This handles nested symlinks and relative path components
|
||||
resolved = os.path.realpath(expanded_path)
|
||||
|
||||
return resolved
|
||||
except (OSError, ValueError):
|
||||
# If resolution fails (e.g., permission denied, invalid path),
|
||||
# return the original path to avoid blocking legitimate operations
|
||||
return file_path
|
||||
|
||||
def _check_condition(self, condition: Condition, tool_name: str,
|
||||
tool_input: Dict[str, Any], input_data: Dict[str, Any] = None) -> bool:
|
||||
"""Check if a single condition matches.
|
||||
@@ -229,10 +196,6 @@ class RuleEngine:
|
||||
if field in tool_input:
|
||||
value = tool_input[field]
|
||||
if isinstance(value, str):
|
||||
# Security fix: resolve symlinks for file_path fields to prevent bypass
|
||||
# CVE-2025-59829: Deny rules could be bypassed via symlinks
|
||||
if field == 'file_path':
|
||||
value = self._resolve_symlink_path(value)
|
||||
return value
|
||||
return str(value)
|
||||
|
||||
@@ -278,18 +241,11 @@ class RuleEngine:
|
||||
elif field == 'old_text' or field == 'old_string':
|
||||
return tool_input.get('old_string', '')
|
||||
elif field == 'file_path':
|
||||
# Security fix: resolve symlinks to prevent deny rule bypass
|
||||
return self._resolve_symlink_path(tool_input.get('file_path', ''))
|
||||
|
||||
elif tool_name == 'Read':
|
||||
# Security fix for CVE-2025-59829: Read tool symlink bypass
|
||||
if field == 'file_path':
|
||||
return self._resolve_symlink_path(tool_input.get('file_path', ''))
|
||||
return tool_input.get('file_path', '')
|
||||
|
||||
elif tool_name == 'MultiEdit':
|
||||
if field == 'file_path':
|
||||
# Security fix: resolve symlinks to prevent deny rule bypass
|
||||
return self._resolve_symlink_path(tool_input.get('file_path', ''))
|
||||
return tool_input.get('file_path', '')
|
||||
elif field in ['new_text', 'content']:
|
||||
# Concatenate all edits
|
||||
edits = tool_input.get('edits', [])
|
||||
|
||||
@@ -45,9 +45,7 @@ def main():
|
||||
event = None
|
||||
if tool_name == 'Bash':
|
||||
event = 'bash'
|
||||
elif tool_name in ['Edit', 'Write', 'MultiEdit', 'Read']:
|
||||
# Include Read tool in file events to check symlink bypass
|
||||
# Security fix for CVE-2025-59829
|
||||
elif tool_name in ['Edit', 'Write', 'MultiEdit']:
|
||||
event = 'file'
|
||||
|
||||
# Load rules
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
{
|
||||
"description": "Security hooks for file access validation and security pattern warnings",
|
||||
"description": "Security reminder hook that warns about potential security issues when editing files",
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/symlink_deny_hook.py"
|
||||
}
|
||||
],
|
||||
"matcher": "Edit|Write|MultiEdit|Read"
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
@@ -18,7 +9,7 @@
|
||||
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/security_reminder_hook.py"
|
||||
}
|
||||
],
|
||||
"matcher": "Edit|Write|MultiEdit|Read"
|
||||
"matcher": "Edit|Write|MultiEdit"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -180,46 +180,10 @@ def save_state(session_id, shown_warnings):
|
||||
pass # Fail silently if we can't save state
|
||||
|
||||
|
||||
def resolve_symlink_path(file_path):
|
||||
"""Resolve symlinks in file path to get canonical path.
|
||||
|
||||
Security fix for CVE-2025-59829: Deny rules could be bypassed by creating
|
||||
a symlink to a restricted file. This method resolves the symlink to its
|
||||
target path so that security patterns are checked against the actual file.
|
||||
|
||||
Args:
|
||||
file_path: The file path that may contain symlinks
|
||||
|
||||
Returns:
|
||||
The canonical path with symlinks resolved, or original path if
|
||||
resolution fails (e.g., file doesn't exist yet)
|
||||
"""
|
||||
if not file_path:
|
||||
return file_path
|
||||
|
||||
try:
|
||||
# Expand user home directory first
|
||||
expanded_path = os.path.expanduser(file_path)
|
||||
|
||||
# Use realpath to resolve all symlinks and get canonical path
|
||||
# This handles nested symlinks and relative path components
|
||||
resolved = os.path.realpath(expanded_path)
|
||||
|
||||
return resolved
|
||||
except (OSError, ValueError):
|
||||
# If resolution fails (e.g., permission denied, invalid path),
|
||||
# return the original path to avoid blocking legitimate operations
|
||||
return file_path
|
||||
|
||||
|
||||
def check_patterns(file_path, content):
|
||||
"""Check if file path or content matches any security patterns."""
|
||||
# Security fix: resolve symlinks before checking patterns
|
||||
# CVE-2025-59829: Security patterns could be bypassed via symlinks
|
||||
resolved_path = resolve_symlink_path(file_path)
|
||||
|
||||
# Normalize path by removing leading slashes
|
||||
normalized_path = resolved_path.lstrip("/")
|
||||
normalized_path = file_path.lstrip("/")
|
||||
|
||||
for pattern in SECURITY_PATTERNS:
|
||||
# Check path-based patterns
|
||||
@@ -277,7 +241,7 @@ def main():
|
||||
tool_input = input_data.get("tool_input", {})
|
||||
|
||||
# Check if this is a relevant tool
|
||||
if tool_name not in ["Edit", "Write", "MultiEdit", "Read"]:
|
||||
if tool_name not in ["Edit", "Write", "MultiEdit"]:
|
||||
sys.exit(0) # Allow non-file tools to proceed
|
||||
|
||||
# Extract file path from tool_input
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Symlink Deny Hook for Claude Code
|
||||
Security fix for CVE-2025-59829: Deny rules could be bypassed via symlinks.
|
||||
|
||||
This hook resolves symlinks before checking file paths against deny patterns,
|
||||
preventing attackers from using symlinks to access restricted files.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from fnmatch import fnmatch
|
||||
|
||||
|
||||
# System directories that should be blocked by default
|
||||
# These match common deny rule patterns
|
||||
BLOCKED_PATHS = [
|
||||
"/etc/**",
|
||||
"/etc/passwd",
|
||||
"/etc/shadow",
|
||||
"/etc/sudoers",
|
||||
"/etc/ssh/**",
|
||||
"/etc/ssl/**",
|
||||
"/root/**",
|
||||
"/var/log/**",
|
||||
"/proc/**",
|
||||
"/sys/**",
|
||||
"/boot/**",
|
||||
]
|
||||
|
||||
|
||||
def resolve_symlink_path(file_path: str) -> str:
|
||||
"""Resolve symlinks in file path to get canonical path.
|
||||
|
||||
Args:
|
||||
file_path: The file path that may contain symlinks
|
||||
|
||||
Returns:
|
||||
The canonical path with symlinks resolved, or original path if
|
||||
resolution fails (e.g., file doesn't exist)
|
||||
"""
|
||||
if not file_path:
|
||||
return file_path
|
||||
|
||||
try:
|
||||
# Expand user home directory first
|
||||
expanded_path = os.path.expanduser(file_path)
|
||||
|
||||
# Use realpath to resolve all symlinks and get canonical path
|
||||
resolved = os.path.realpath(expanded_path)
|
||||
|
||||
return resolved
|
||||
except (OSError, ValueError):
|
||||
return file_path
|
||||
|
||||
|
||||
def is_path_blocked(resolved_path: str, original_path: str) -> tuple:
|
||||
"""Check if the resolved path matches any blocked patterns.
|
||||
|
||||
Only blocks if:
|
||||
1. The path was a symlink (resolved != original)
|
||||
2. The resolved path matches a blocked pattern
|
||||
|
||||
Args:
|
||||
resolved_path: The canonical path after symlink resolution
|
||||
original_path: The original path before resolution
|
||||
|
||||
Returns:
|
||||
Tuple of (is_blocked: bool, reason: str)
|
||||
"""
|
||||
# Only apply symlink protection if path was actually a symlink
|
||||
original_real = os.path.realpath(os.path.expanduser(original_path))
|
||||
if original_real == resolved_path:
|
||||
# Check if original was a symlink
|
||||
expanded_original = os.path.expanduser(original_path)
|
||||
if not os.path.islink(expanded_original):
|
||||
# Not a symlink, allow normal deny rule checking to handle this
|
||||
return False, ""
|
||||
|
||||
# Check if resolved path matches any blocked patterns
|
||||
for pattern in BLOCKED_PATHS:
|
||||
if pattern.endswith("/**"):
|
||||
# Directory wildcard pattern
|
||||
base_dir = pattern[:-3]
|
||||
if resolved_path.startswith(base_dir + "/") or resolved_path == base_dir:
|
||||
return True, f"Symlink bypass blocked: '{original_path}' resolves to '{resolved_path}' which matches blocked pattern '{pattern}'"
|
||||
elif fnmatch(resolved_path, pattern):
|
||||
return True, f"Symlink bypass blocked: '{original_path}' resolves to '{resolved_path}' which matches blocked pattern '{pattern}'"
|
||||
|
||||
return False, ""
|
||||
|
||||
|
||||
def main():
|
||||
"""Main hook function."""
|
||||
try:
|
||||
input_data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError:
|
||||
sys.exit(0) # Allow on parse error
|
||||
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
tool_input = input_data.get("tool_input", {})
|
||||
|
||||
# Only check file-related tools
|
||||
if tool_name not in ["Read", "Edit", "Write", "MultiEdit"]:
|
||||
sys.exit(0)
|
||||
|
||||
# Extract file path
|
||||
file_path = tool_input.get("file_path", "")
|
||||
if not file_path:
|
||||
sys.exit(0)
|
||||
|
||||
# Resolve symlinks
|
||||
resolved_path = resolve_symlink_path(file_path)
|
||||
|
||||
# Check if blocked
|
||||
is_blocked, reason = is_path_blocked(resolved_path, file_path)
|
||||
|
||||
if is_blocked:
|
||||
# Output denial response
|
||||
response = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny"
|
||||
},
|
||||
"systemMessage": f"Security: {reason}"
|
||||
}
|
||||
print(json.dumps(response))
|
||||
sys.exit(0)
|
||||
|
||||
# Allow the operation
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Comments on a GitHub issue with a list of potential duplicates.
|
||||
# Usage: ./comment-on-duplicates.sh --base-issue 123 --potential-duplicates 456 789 101
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO="anthropics/claude-code"
|
||||
BASE_ISSUE=""
|
||||
DUPLICATES=()
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--base-issue)
|
||||
BASE_ISSUE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--potential-duplicates)
|
||||
shift
|
||||
while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do
|
||||
DUPLICATES+=("$1")
|
||||
shift
|
||||
done
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate base issue
|
||||
if [[ -z "$BASE_ISSUE" ]]; then
|
||||
echo "Error: --base-issue is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$BASE_ISSUE" =~ ^[0-9]+$ ]]; then
|
||||
echo "Error: --base-issue must be a number, got: $BASE_ISSUE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate duplicates
|
||||
if [[ ${#DUPLICATES[@]} -eq 0 ]]; then
|
||||
echo "Error: --potential-duplicates requires at least one issue number" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ${#DUPLICATES[@]} -gt 3 ]]; then
|
||||
echo "Error: --potential-duplicates accepts at most 3 issues" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for dup in "${DUPLICATES[@]}"; do
|
||||
if ! [[ "$dup" =~ ^[0-9]+$ ]]; then
|
||||
echo "Error: duplicate issue must be a number, got: $dup" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate that base issue exists
|
||||
if ! gh issue view "$BASE_ISSUE" --repo "$REPO" &>/dev/null; then
|
||||
echo "Error: issue #$BASE_ISSUE does not exist in $REPO" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate that all duplicate issues exist
|
||||
for dup in "${DUPLICATES[@]}"; do
|
||||
if ! gh issue view "$dup" --repo "$REPO" &>/dev/null; then
|
||||
echo "Error: issue #$dup does not exist in $REPO" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Build comment body
|
||||
COUNT=${#DUPLICATES[@]}
|
||||
if [[ $COUNT -eq 1 ]]; then
|
||||
HEADER="Found 1 possible duplicate issue:"
|
||||
else
|
||||
HEADER="Found $COUNT possible duplicate issues:"
|
||||
fi
|
||||
|
||||
BODY="$HEADER"$'\n\n'
|
||||
INDEX=1
|
||||
for dup in "${DUPLICATES[@]}"; do
|
||||
BODY+="$INDEX. https://github.com/$REPO/issues/$dup"$'\n'
|
||||
((INDEX++))
|
||||
done
|
||||
|
||||
BODY+=$'\n'"This issue will be automatically closed as a duplicate in 3 days."$'\n\n'
|
||||
BODY+="- If your issue is a duplicate, please close it and 👍 the existing issue instead"$'\n'
|
||||
BODY+="- To prevent auto-closure, add a comment or 👎 this comment"$'\n\n'
|
||||
BODY+="🤖 Generated with [Claude Code](https://claude.ai/code)"
|
||||
|
||||
# Post the comment
|
||||
gh issue comment "$BASE_ISSUE" --repo "$REPO" --body "$BODY"
|
||||
|
||||
echo "Posted duplicate comment on issue #$BASE_ISSUE"
|
||||
Reference in New Issue
Block a user