Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "passthru",
"version": "0.5.1",
"version": "0.5.2",
"description": "Regex-based permission rules for Claude Code via hooks",
"owner": {
"name": "nnemirovsky"
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "passthru",
"version": "0.5.1",
"version": "0.5.2",
"description": "Regex-based permission rules for Claude Code via hooks",
"license": "MIT"
}
27 changes: 24 additions & 3 deletions hooks/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,9 @@ overlay_available() {
#
# Mode behavior:
# bypassPermissions: always 0 (everything auto-allowed).
# acceptEdits: 0 for Write/Edit/NotebookEdit/MultiEdit when the
# target file_path resolves inside cwd. Non-edit tools
# in acceptEdits return 1.
# acceptEdits: superset of default. 0 for Write/Edit/NotebookEdit/
# MultiEdit + Read/Grep/Glob/NotebookRead/LS when the
# target path resolves inside cwd.
# default (+ empty mode value): 0 for read-only tools
# (Read/Grep/Glob/NotebookRead/LS) when the target path
# is inside cwd. Everything else returns 1, including
Expand Down Expand Up @@ -408,6 +408,8 @@ permission_mode_auto_allows() {

case "$mode" in
acceptEdits)
# acceptEdits is a superset of default: everything default auto-allows
# (Read/Grep/Glob/LS inside cwd) PLUS edit tools inside cwd.
case "$tool_name" in
Write|Edit|NotebookEdit|MultiEdit)
local fp
Expand All @@ -417,6 +419,25 @@ permission_mode_auto_allows() {
fi
return 1
;;
Read|NotebookRead)
local fp
fp="$(jq -r '.file_path // .notebook_path // ""' <<<"$tool_input" 2>/dev/null || printf '')"
if _pm_path_inside_cwd "$fp" "$cwd"; then
return 0
fi
return 1
;;
Grep|Glob|LS)
local gp
gp="$(jq -r '.path // ""' <<<"$tool_input" 2>/dev/null || printf '')"
if [ -z "$gp" ]; then
return 0
fi
if _pm_path_inside_cwd "$gp" "$cwd"; then
return 0
fi
return 1
;;
*)
return 1
;;
Expand Down
41 changes: 34 additions & 7 deletions hooks/handlers/pre-tool-use.sh
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ fi
# that should never trigger the overlay. Pass them through unconditionally.
if [ "$MATCHED" != "ask" ]; then
case "$TOOL_NAME" in
ToolSearch|TaskCreate|TaskUpdate|TaskGet|TaskList|TaskOutput|TaskStop|\
ToolSearch|Skill|TaskCreate|TaskUpdate|TaskGet|TaskList|TaskOutput|TaskStop|\
AskUserQuestion|SendMessage|EnterPlanMode|ExitPlanMode|ScheduleWakeup|\
CronCreate|CronDelete|CronList|Monitor|LSP|RemoteTrigger|\
EnterWorktree|ExitWorktree|TeamCreate|TeamDelete)
Expand All @@ -497,12 +497,38 @@ if [ "$MATCHED" != "ask" ]; then
esac
fi

# --- 8. Overlay path -------------------------------------------------------
# Passthru handles ALL non-internal tool calls. There is no mode-based
# auto-allow shortcut. Every unmatched call goes to the overlay so the user
# always sees a prompt. CC's native dialog only fires as a fallback when the
# user explicitly cancels the overlay (Esc) or the overlay is unavailable.
#
# --- 8. Mode-based auto-allow -----------------------------------------------
# Replicate CC's per-mode auto-allow logic within passthru. Calls that CC
# would silently approve (e.g. Read inside cwd in default mode, Write inside
# cwd in acceptEdits mode) get an explicit allow from passthru so the overlay
# does not fire for routine operations. Passthru emits allow (not continue),
# keeping the decision on our side rather than falling through to CC.
if [ "$MATCHED" != "ask" ]; then
if permission_mode_auto_allows "$PERMISSION_MODE" "$TOOL_NAME" "$TOOL_INPUT" "$CC_CWD" 2>/dev/null; then
MSG="passthru mode-allow: ${PERMISSION_MODE:-default}"
emit_decision "allow" "$MSG"
audit_write_line "allow" "$TOOL_NAME" "mode:${PERMISSION_MODE:-default}" "" "" "$TOOL_USE_ID" "passthru-mode"
exit 0
fi
fi

# --- 9. Write tools -> native dialog (for diff rendering) ------------------
# Write/Edit/NotebookEdit that weren't mode-auto-allowed (step 8) should
# fall through to CC's native dialog which renders diffs. The overlay can't
# show diffs, so forcing Esc for every edit is bad UX. An explicit ask-rule
# match still routes to the overlay (user opted in).
if [ "$MATCHED" != "ask" ]; then
case "$TOOL_NAME" in
Write|Edit|NotebookEdit|MultiEdit)
emit_decision "ask" "passthru: write tool, deferring to native dialog for diff"
audit_write_line "ask" "$TOOL_NAME" "write-tool native fallback" "" "" "$TOOL_USE_ID"
audit_write_breadcrumb "$TOOL_USE_ID" "$TOOL_NAME" "$TOOL_INPUT"
exit 0
;;
esac
fi

# --- 10. Overlay path ------------------------------------------------------
# Reached when either:
# * an ask[] rule matched, or
# * no rule matched AND mode did NOT auto-allow.
Expand Down Expand Up @@ -582,6 +608,7 @@ fi
export PASSTHRU_OVERLAY_RESULT_FILE="$OVERLAY_RESULT"
export PASSTHRU_OVERLAY_TOOL_NAME="$TOOL_NAME"
export PASSTHRU_OVERLAY_TOOL_INPUT_JSON="$TOOL_INPUT"
export PASSTHRU_OVERLAY_CWD="$CC_CWD"

# Invoke the overlay and capture its exit code. We have an ERR trap in place
# (converts unexpected errors to fail-open passthrough), so we cannot rely on
Expand Down
2 changes: 1 addition & 1 deletion hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/handlers/pre-tool-use.sh",
"timeout": 75
"timeout": 300
}
]
}
Expand Down
Loading
Loading