From 6acb133efd2c07a50a5c7f18d1b05eb604fc53ce Mon Sep 17 00:00:00 2001 From: RomanBOGR Date: Fri, 11 Sep 2026 02:42:48 +0300 Subject: [PATCH 1/2] fix: show the question in AskUserQuestion permission notifications The preview in on-permission-request.sh handles .command and .file_path and falls back to tostring[0:80] for everything else. For AskUserQuestion that fallback dumps raw JSON, so the notification truncates before the question itself. Add an elif branch for .questions, plus four tests covering Bash, Write, AskUserQuestion and the unknown-tool fallback. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/warp/scripts/on-permission-request.sh | 2 +- plugins/warp/tests/test-hooks.sh | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/plugins/warp/scripts/on-permission-request.sh b/plugins/warp/scripts/on-permission-request.sh index 7d46ed2..9726d88 100755 --- a/plugins/warp/scripts/on-permission-request.sh +++ b/plugins/warp/scripts/on-permission-request.sh @@ -22,7 +22,7 @@ TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}' 2>/dev/null) [ -z "$TOOL_INPUT" ] && TOOL_INPUT='{}' # Build a human-readable summary -TOOL_PREVIEW=$(echo "$INPUT" | jq -r '(.tool_input | if .command then .command elif .file_path then .file_path else (tostring | .[0:80]) end) // ""' 2>/dev/null) +TOOL_PREVIEW=$(echo "$INPUT" | jq -r '(.tool_input | if .command then .command elif .file_path then .file_path elif .questions then (.questions[0].question // "") else (tostring | .[0:80]) end) // ""' 2>/dev/null) SUMMARY="Wants to run $TOOL_NAME" if [ -n "$TOOL_PREVIEW" ]; then if [ ${#TOOL_PREVIEW} -gt 120 ]; then diff --git a/plugins/warp/tests/test-hooks.sh b/plugins/warp/tests/test-hooks.sh index 754bdd0..e679138 100755 --- a/plugins/warp/tests/test-hooks.sh +++ b/plugins/warp/tests/test-hooks.sh @@ -266,6 +266,36 @@ unset CLAUDE_CODE_VERSION HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd)" +echo "" +echo "=== Permission request preview ===" + +# Runs on-permission-request.sh end to end and pulls .summary back out of the +# OSC sequence, so the jq preview expression is covered by the suite. +permission_summary() { + echo "$1" | WARP_CLI_AGENT_PROTOCOL_VERSION=1 \ + WARP_CLIENT_VERSION="v9999.99.99.99.99.stable_99" \ + CLAUDE_CODE_VERSION="9999.0.0" \ + bash "$HOOK_DIR/on-permission-request.sh" 2>/dev/null | + jq -r '.terminalSequence // empty' | + sed 's/^.*warp:\/\/cli-agent;//' | tr -d '\007' | + jq -r '.summary // empty' +} + +echo "" +echo "--- Tool preview ---" + +assert_eq "Bash shows the command" "Wants to run Bash: git push origin main" \ + "$(permission_summary '{"tool_name":"Bash","tool_input":{"command":"git push origin main"}}')" + +assert_eq "Write shows the path" "Wants to run Write: /tmp/a.txt" \ + "$(permission_summary '{"tool_name":"Write","tool_input":{"file_path":"/tmp/a.txt","content":"hi"}}')" + +assert_eq "AskUserQuestion shows the question" "Wants to run AskUserQuestion: Deploy now or wait?" \ + "$(permission_summary '{"tool_name":"AskUserQuestion","tool_input":{"questions":[{"question":"Deploy now or wait?","header":"Deploy"}]}}')" + +assert_eq "unknown tool falls back to the raw input" 'Wants to run WebFetch: {"url":"https://example.com"}' \ + "$(permission_summary '{"tool_name":"WebFetch","tool_input":{"url":"https://example.com"}}')" + echo "" echo "=== Routing ===" From a698487d5355fb52a433367a66e26591da9df3d9 Mon Sep 17 00:00:00 2001 From: RomanBOGR Date: Fri, 18 Sep 2026 19:19:04 +0300 Subject: [PATCH 2/2] fix: title Stop notifications with the human's prompt, not synthetic entries Transcript entries of type "user" are not only what the human typed. Background-task notifications, blocking-hook feedback, system reminders, messages from other sessions and subagent prompts all arrive under the same type, so taking the last one titled notifications like '\n --- plugins/warp/scripts/on-stop.sh | 36 +++++++++++++++++++++--------- plugins/warp/tests/test-hooks.sh | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/plugins/warp/scripts/on-stop.sh b/plugins/warp/scripts/on-stop.sh index 4163bb9..80f00f7 100755 --- a/plugins/warp/scripts/on-stop.sh +++ b/plugins/warp/scripts/on-stop.sh @@ -36,18 +36,32 @@ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then # containing {type:"text"} blocks. Tool-result messages have content arrays # containing only {type:"tool_result"} blocks. We filter to messages that # have at least one "text" block (or are a plain string). + # "user" entries are not only what the human typed. Background-task + # notifications, blocking-hook feedback, system reminders and messages from + # other sessions arrive under the same type, and taking the last one made + # notifications read "'\n 0 then . - else empty - end - ] | last | - if .message.content | type == "array" - then [.message.content[] | select(.type == "text") | .text] | join(" ") - else .message.content // empty - end + def text: (if .message.content | type == "string" + then .message.content + else ([.message.content[]? | select(.type == "text") | .text] | join(" ")) + end) + | gsub("\\[Image[^\\]]*\\]"; "") + | gsub("^\\s+|\\s+$"; ""); + def synthetic: startswith("<") # , , + # , + or startswith("Stop hook feedback:") + or startswith("Another Claude session sent a message") + or startswith("[Request interrupted") + or startswith("Caveat:"); + [ .[] + | select(.type == "user") + | select(has("isSidechain") and .isSidechain | not) + | text + | select(. != null and . != "") + | select(synthetic | not) + ] | last // "" ' "$TRANSCRIPT_PATH" 2>/dev/null) # Get the last assistant response diff --git a/plugins/warp/tests/test-hooks.sh b/plugins/warp/tests/test-hooks.sh index e679138..cd84837 100755 --- a/plugins/warp/tests/test-hooks.sh +++ b/plugins/warp/tests/test-hooks.sh @@ -266,6 +266,44 @@ unset CLAUDE_CODE_VERSION HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd)" +echo "" +echo "=== Stop query selection ===" + +# Feeds a transcript through on-stop.sh and pulls .query back out, so the +# synthetic-entry filtering is covered by the suite. +stop_query() { + local file + file=$(mktemp) + printf '%s\n' "$@" > "$file" + echo "{\"session_id\":\"s\",\"cwd\":\"/tmp/p\",\"transcript_path\":\"$file\"}" | + WARP_CLI_AGENT_PROTOCOL_VERSION=1 \ + WARP_CLIENT_VERSION="v9999.99.99.99.99.stable_99" \ + CLAUDE_CODE_VERSION="9999.0.0" \ + bash "$HOOK_DIR/on-stop.sh" 2>/dev/null | + jq -r '.terminalSequence // empty' | + sed 's/^.*warp:\/\/cli-agent;//' | tr -d '\007' | + jq -r '.query // empty' + rm -f "$file" +} + +HUMAN='{"type":"user","message":{"role":"user","content":"ship the release"}}' +NOTIF='{"type":"user","message":{"role":"user","content":"\nbuild done"}}' +HOOKFB='{"type":"user","message":{"role":"user","content":"Stop hook feedback:\n[hook] do X"}}' +PEER='{"type":"user","message":{"role":"user","content":"Another Claude session sent a message:\nhi"}}' +IMGONLY='{"type":"user","message":{"role":"user","content":"[Image: source: /tmp/a.png]"}}' +SIDE='{"type":"user","isSidechain":true,"message":{"role":"user","content":"subagent prompt"}}' +REPLY='{"type":"assistant","message":{"content":[{"type":"text","text":"done"}]}}' + +echo "" +echo "--- Query skips synthetic user entries ---" + +assert_eq "plain prompt is used" "ship the release" "$(stop_query "$HUMAN" "$REPLY")" +assert_eq "task notification skipped" "ship the release" "$(stop_query "$HUMAN" "$REPLY" "$NOTIF")" +assert_eq "stop hook feedback skipped" "ship the release" "$(stop_query "$HUMAN" "$REPLY" "$HOOKFB")" +assert_eq "peer message skipped" "ship the release" "$(stop_query "$HUMAN" "$REPLY" "$PEER")" +assert_eq "subagent prompt skipped" "ship the release" "$(stop_query "$HUMAN" "$REPLY" "$SIDE")" +assert_eq "image-only message leaves no path" "ship the release" "$(stop_query "$HUMAN" "$REPLY" "$IMGONLY")" + echo "" echo "=== Permission request preview ==="