Skip to content

Quote ${CLAUDE_PLUGIN_ROOT} in hooks.json so paths with spaces work#26

Open
stuli1989 wants to merge 1 commit intowarpdotdev:mainfrom
stuli1989:fix/quote-plugin-root-in-hooks
Open

Quote ${CLAUDE_PLUGIN_ROOT} in hooks.json so paths with spaces work#26
stuli1989 wants to merge 1 commit intowarpdotdev:mainfrom
stuli1989:fix/quote-plugin-root-in-hooks

Conversation

@stuli1989
Copy link
Copy Markdown

Summary

On Windows (or any platform where the plugin directory path contains a space), every hook in plugins/warp/hooks/hooks.json fails because ${CLAUDE_PLUGIN_ROOT} is expanded into the command string without quotes. The shell word-splits the resulting path on the space and tries to execute the first token as a command.

Repro

  • Windows account whose username has a space, e.g. C:\Users\First Last
  • Install Claude Code and enable warp@claude-code-warp
  • Submit any prompt

Observed

UserPromptSubmit operation blocked by hook:
[${CLAUDE_PLUGIN_ROOT}/scripts/on-prompt-submit.sh]:
/c/Users/First: line 1: syntax error near unexpected token `('
/c/Users/First: line 1: `[9ADC:4BD8][...]i001: Burn v3.14.1.8722, Windows v10.0 (Build 26200: Service Pack 0), path: C:\WINDOWS\Temp\{...}\.cr\VC_redist.x86.exe'

The path /c/Users/First Last/.claude/plugins/cache/claude-code-warp/warp/2.0.0/scripts/on-prompt-submit.sh is split into /c/Users/First + Last/.... Since that truncated path isn't an executable, the shell falls through and ends up interpreting unrelated bytes on disk (in my case a stray VC_redist bootstrapper log) as a script, producing a confusing syntax error.

All six hooks are affected: SessionStart, Stop, Notification, PermissionRequest, UserPromptSubmit, PostToolUse.

Root cause

Each hook is registered as:

"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-prompt-submit.sh"

When Claude Code hands the expanded string to the shell, there are no quotes, so any whitespace in the path breaks word-splitting. The scripts themselves already quote "$SCRIPT_DIR" correctly — the issue is purely at the registration layer.

Fix

Wrap each command value in embedded double quotes so the shell sees a quoted path after expansion:

-"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-prompt-submit.sh"
+"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/on-prompt-submit.sh\""

Applied to all six hook entries. No behavioural change for users whose paths don't contain spaces.

Verification

Tested locally on C:\Users\Kshitij Shah\... — prompt submission, session start/stop, notifications, permission requests, and post-tool-use events all fire correctly after the change. JSON still parses cleanly.

On Windows (and any platform where the user's home/plugin path
contains a space), the unquoted ${CLAUDE_PLUGIN_ROOT} expansion in
each hook command causes the shell to word-split on the space,
so e.g. `C:\Users\First Last\.claude\plugins\...` is split into
`C:\Users\First` + `Last\.claude\...`. The shell then tries to
execute the first token as a command, typically failing with a
confusing syntax error when it falls back to reading unrelated
bytes on disk.

Wrapping each command in embedded double quotes keeps the path
as a single token after variable expansion, with no behavioural
change for users whose paths don't contain spaces.

All six hooks are affected: SessionStart, Stop, Notification,
PermissionRequest, UserPromptSubmit, PostToolUse.

Co-Authored-By: Oz <oz-agent@warp.dev>
yigitkonur added a commit to yigitkonur/claude-code-warp that referenced this pull request Apr 21, 2026
Covers every hook in the Claude Code lifecycle so the Warp sidebar
reflects real agent state instead of getting stuck on In progress /
Blocked.

State coverage
- PostToolUse now fires for every tool (matcher removed). A `.blocked`
  marker dropped by PermissionRequest gates emission so we don't flood
  Warp with tool_complete on every Read/Glob/Grep — auto-approved tools
  skip emission entirely. Fixes stuck-Blocked without re-introducing the
  200+ GB leak reported in warpdotdev#22.
- New PreToolUse hook emits tool_start for live per-tool sidebar signal.
- SubagentStart/Stop, PermissionDenied, PostToolUseFailure, PreCompact,
  PostCompact, CwdChanged, SessionEnd get dedicated scripts with a
  v3-only event gate (WARP_CLI_AGENT_V3_EVENTS=1). When unset, they emit
  v2-compatible fallbacks so the sidebar state still moves on stable
  Warp builds.

Fresh-tab parity with Gemini/Droid
- on-session-start.sh for source=startup emits nothing. Warp's
  process-detection registers the sidebar row without a state pill,
  matching Gemini CLI and Factory/Droid (neither registers a Warp hook).
  First OSC event fires on UserPromptSubmit. plugin_version moved onto
  prompt_submit so Warp's outdated-plugin banner still has a signal.
- For resume/clear/compact, session_start still emits with enrichment
  since those genuinely change state.

Payload hygiene
- build-payload.sh strips empty-string --arg values so Warp doesn't see
  model:"" / permission_mode:"" as "still initializing".
- Early-exit on empty session_id avoids stray emissions to the wrong row.
- utf8_truncate helper — codepoint-aware, preserves multi-byte integrity.

Reliability
- PPID-walk TTY detection in warp-notify.sh (ports warpdotdev#19). Hook
  subprocesses often lack /dev/tty; walk the parent chain to find one.
- hooks.json quotes ${CLAUDE_PLUGIN_ROOT} (ports warpdotdev#26) — Windows paths
  with spaces no longer break every hook.
- async:true removed from Stop/StopFailure/SubagentStop. Claude Code was
  killing async hooks before they finished emitting in -p mode, causing
  tool_complete / stop emissions to silently drop.
- -p headless mode: on-session-{start,end}.sh skip emission with no tty.

Configurability
- CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1 opts out of prompt-as-title
  (ports warpdotdev#24 — users driving tab titles via kitty/tmux/shell hooks).
- WARP_PLUGIN_DISABLE_PROJECT=1 drops the project envelope field
  (ports warpdotdev#23).

Debugging
- New scripts/warp-log.sh logs every hook input and every OSC emit to
  \${TMPDIR:-/tmp}/warp-claude-\${SESSION_ID}.log with sub-second
  timestamps. /tmp/warp-claude-latest.log is a symlink for tail -f.
  WARP_KEEP_LOGS=1 preserves logs past SessionEnd.

Prompt lifecycle
- session_title on prompt_submit labels the sidebar row with the first
  prompt text instead of the generic "Claude Code".
- duration_ms on stop, computed from a t0 timestamp stashed on
  prompt_submit.
yigitkonur added a commit to yigitkonur/claude-code-warp that referenced this pull request Apr 21, 2026
Covers:
  - What the v3 fork changes relative to upstream v2 (table of fixes
    with links to resolved upstream issues warpdotdev#19/warpdotdev#22/warpdotdev#23/warpdotdev#24/warpdotdev#26)
  - Install / uninstall / rollback commands
  - Env-var configuration: WARP_CLI_AGENT_V3_EVENTS, WARP_KEEP_LOGS,
    CLAUDE_CODE_DISABLE_TERMINAL_TITLE, WARP_PLUGIN_DISABLE_PROJECT
  - R5 fallback mapping (v3-only events → v2-compatible shapes)
  - Debugging workflow via `tail -f /tmp/warp-claude-latest.log` with
    a sample event trace from a real session
  - Running the test suite
yigitkonur added a commit to yigitkonur/claude-code-warp that referenced this pull request Apr 21, 2026
3.0.0 → 3.0.5 captures the expanded sidebar state coverage, fresh-tab
zero-emission (Droid/Gemini parity), PostToolUse memory-leak gate, and
five upstream-issue backports: warpdotdev#19 (TTY PPID walk), warpdotdev#22 (leak
mitigation via .blocked marker), warpdotdev#23 (WARP_PLUGIN_DISABLE_PROJECT),
warpdotdev#24 (CLAUDE_CODE_DISABLE_TERMINAL_TITLE), warpdotdev#26 (quote CLAUDE_PLUGIN_ROOT).
@harryalbert harryalbert self-requested a review April 22, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant