Skip to content

Commit aabc7ce

Browse files
nhortonclaude
andauthored
fix: plugin hooks always invoke deepwork via uvx (#390)
* refactor: always invoke deepwork via uvx in plugin hooks Switches plugin hook scripts to `uvx deepwork ...` unconditionally, matching how the MCP server is launched in plugins/claude/.mcp.json. Rationale: a `command -v deepwork` fallback was not enough. A user-level `uv tool install deepwork` pinned to an older release (observed: 0.10.0 on a system where the plugin MCP server uses 0.13.8) still wins PATH lookup outside the project's nix/direnv shell, and that stale binary lacks newer hook modules — producing "Hook '...' not found" errors on every Bash tool use, which Claude Code surfaces as a failed hook. By always using `uvx deepwork`, hooks resolve against the same uvx cache the MCP server populated, so the hook version is guaranteed to match the server. Also adds formal requirement PLUG-REQ-001.15 (Hook Script CLI Invocation) and tightens the `claude_plugin_hook_deepwork_invocation` review rule to enforce `uvx deepwork` (not just presence of a fallback). Fills out header comments and adds `set -euo pipefail` to the two hook scripts that lacked them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: remove uv tool install from flake shellHook The editable uv tool install is unnecessary now that plugin hooks invoke deepwork via uvx directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: sync cli_configuration.md hook listing with actual files The plugin structure tree only listed hooks.json and post_commit_reminder.sh, missing deepschema_write.sh, post_compact.sh, and startup_context.sh. Surfaced by the update_documents_relating_to_src_deepwork review rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent db01ca6 commit aabc7ce

8 files changed

Lines changed: 89 additions & 62 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- New `PLUG-REQ-001.15: Hook Script CLI Invocation` requirement in `doc/specs/deepwork/cli_plugins/PLUG-REQ-001-claude-code-plugin.md`
13+
1214
### Changed
1315

16+
- `claude_plugin_hook_deepwork_invocation` review rule now requires plugin hook scripts to invoke the CLI via `uvx deepwork` instead of merely providing a `uvx deepwork` fallback (PLUG-REQ-001.15)
17+
- Plugin hook scripts (`post_commit_reminder.sh`, `deepschema_write.sh`, `post_compact.sh`) now invoke the `deepwork` CLI exclusively via `uvx deepwork ...`, matching the MCP server launch in `plugins/claude/.mcp.json`
18+
- Flake `shellHook` no longer runs `uv tool install -e` — the editable user-level `deepwork` install is redundant now that plugin hooks go through `uvx`
19+
1420
### Fixed
1521

22+
- Plugin hooks no longer fail when the end user has a stale user-level `deepwork` install (e.g., `uv tool install deepwork` pinned to an older release) that wins PATH lookup but lacks the hook module being requested. The 0.13.9 fallback still used PATH first; this release bypasses PATH entirely so hooks resolve to the same `uvx` cache that the MCP server populated
23+
1624
### Removed
1725
## [0.13.9] - 2026-04-16
1826

1927
### Added
2028

2129
- New `claude_plugin_hook_deepwork_invocation` review rule in `plugins/claude/.deepreview` that flags plugin hook scripts which call bare `deepwork` without a `uvx deepwork` fallback
22-
23-
### Changed
24-
25-
### Fixed
26-
27-
- Plugin hook scripts (`post_commit_reminder.sh`, `deepschema_write.sh`, `post_compact.sh`) now fall back to `uvx deepwork` when the bare `deepwork` binary is not on PATH. End-user installs launch the MCP server via `uvx deepwork serve`, so `deepwork` is not available as a command — previously these hooks failed with exit 127 on every Bash tool use, and Claude Code reported them as failed PostToolUse hooks (regression introduced in PR #361)
28-
29-
### Removed
3030
## [0.13.8] - 2026-04-14
3131

3232
### Added

doc/platforms/claude/cli_configuration.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,10 @@ plugins/claude/
250250
│ └── configure_reviews/SKILL.md # Set up review rules
251251
├── hooks/ # Hook configuration
252252
│ ├── hooks.json
253-
│ └── post_commit_reminder.sh
253+
│ ├── deepschema_write.sh # PostToolUse: validate writes against DeepSchemas
254+
│ ├── post_commit_reminder.sh # PostToolUse: nudge /review after git commit
255+
│ ├── post_compact.sh # SessionStart(compact): restore workflow context
256+
│ └── startup_context.sh # SessionStart/SubagentStart: inject session/agent IDs
254257
└── .mcp.json # MCP server config (uvx deepwork serve)
255258
```
256259

doc/specs/deepwork/cli_plugins/PLUG-REQ-001-claude-code-plugin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,8 @@ The Claude Code plugin is the primary distribution mechanism for DeepWork on the
107107
4. The agent body MUST instruct the subagent to read the instruction file from the user prompt, perform the review against the criteria in that file, and call `mark_review_as_passed` to report results.
108108
5. The agent body MUST instruct the subagent not to edit files and not to explore beyond what the review instructions direct.
109109
6. When the review formatter renders tasks with no per-rule agent persona specified (`agent_name` is `None`), it MUST default to `"reviewer"` as the `subagent_type` (see REVIEW-REQ-006.3.3c).
110+
111+
### PLUG-REQ-001.15: Hook Script CLI Invocation
112+
113+
1. Plugin hook scripts under `plugins/claude/hooks/` MUST invoke the `deepwork` CLI via `uvx deepwork ...`, never via a bare `deepwork` lookup on `PATH` (including `uv run deepwork` or absolute paths to `PATH`-installed binaries). This matches the MCP server launch command in `plugins/claude/.mcp.json` (`uvx deepwork serve`) so that hooks and the MCP server resolve to the same `deepwork` version from the same `uvx` cache.
114+
2. Plugin hook scripts MUST NOT rely on a `deepwork` binary resolved through `PATH`, because (a) the binary is absent from `PATH` in end-user installs that run the plugin via `uvx`, which produces exit 127, and (b) when present via a user-level install such as `uv tool install deepwork`, it can be older than the Python module the hook is asking for, which produces "Hook '...' not found" or similar errors. Both failure modes surface to Claude Code as a failed hook.

flake.nix

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,6 @@
8282
uv sync --extra dev --quiet 2>/dev/null || true
8383
export PATH="$REPO_ROOT/.venv/bin:$PATH"
8484
85-
# Also register as a uv tool so `uvx deepwork serve` uses local source
86-
uv tool install -e "$REPO_ROOT" --quiet 2>/dev/null || true
87-
8885
# Create claude wrapper script so direnv (which can't export functions) works
8986
_claude_real=$(PATH="$(echo "$PATH" | sed "s|$REPO_ROOT/.venv/bin:||g")" command -v claude)
9087
if [ -n "$_claude_real" ]; then

plugins/claude/.deepreview

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,40 @@
11
claude_plugin_hook_deepwork_invocation:
2-
description: "Plugin hook scripts must fall back to `uvx deepwork` when the bare binary is not on PATH."
2+
description: "PLUG-REQ-001.15: Plugin hook scripts must invoke the `deepwork` CLI via `uvx deepwork`, never via a bare binary on PATH."
33
match:
44
include:
55
- "hooks/*.sh"
66
review:
77
strategy: individual
88
instructions: |
99
Plugin hook scripts run in end-user installs where the MCP server is
10-
launched via `uvx deepwork serve` (see plugins/claude/.mcp.json). In
11-
that environment the bare `deepwork` binary is NOT on PATH. Any hook
12-
that calls `deepwork ...` (or `uv run deepwork ...`) directly will
13-
exit 127, and Claude Code reports it as a failed hook on every Bash
14-
tool use. Regression history: PR #361 reintroduced this exact bug.
10+
launched via `uvx deepwork serve` (see plugins/claude/.mcp.json) and
11+
the bare `deepwork` binary may be missing from PATH, or present but
12+
stale — e.g., a user-level `uv tool install deepwork` pinned to an
13+
older release. A stale bare binary produces errors like "Hook
14+
'...' not found" (when the hook module did not exist in that
15+
version) on every Bash tool use, which Claude Code surfaces as a
16+
failed hook.
1517

16-
For each `deepwork` (or `uv run deepwork`) invocation in this script,
17-
verify it uses a fallback pattern equivalent to:
18+
To avoid both failure modes, plugin hook scripts MUST invoke the
19+
CLI the same way the MCP server is launched: via `uvx deepwork`.
20+
A bare `deepwork ...` (or `uv run deepwork ...`, or any invocation
21+
that uses PATH lookup) is a FAIL.
22+
23+
Expected pattern:
1824

1925
```bash
20-
if command -v deepwork >/dev/null 2>&1; then
21-
echo "${INPUT}" | deepwork hook some_hook
22-
else
23-
echo "${INPUT}" | uvx deepwork hook some_hook
24-
fi
26+
echo "${INPUT}" | uvx deepwork hook some_hook
2527
```
2628

27-
A bare `deepwork ...` call without a `command -v deepwork` guard (or
28-
equivalent fallback to `uvx deepwork ...`) is a FAIL.
29+
For each `deepwork` invocation in this script, verify the command
30+
prefix is `uvx deepwork`. Shell word-splitting counts — the word
31+
immediately before `deepwork` on the command line must be `uvx`.
2932

3033
Output Format:
31-
- PASS: Every `deepwork` invocation has a `uvx` fallback, or the
34+
- PASS: Every `deepwork` invocation is prefixed with `uvx`, or the
3235
script makes no `deepwork` calls.
33-
- FAIL: List each unguarded `deepwork` invocation with its line
34-
number and the suggested fallback edit.
36+
- FAIL: List each non-`uvx` `deepwork` invocation with its line
37+
number and the required edit.
3538

3639
claude_plugin_skill_instructions:
3740
description: "PLUG-REQ-001 & REVIEW-REQ-007: Verify skill instructions adequately convey behavioral requirements."
Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
#!/usr/bin/env bash
2-
# DeepSchema write hook
3-
# PostToolUse hook for Write/Edit - validates files against applicable DeepSchemas
4-
# Falls back to `uvx deepwork` so end-user installs (where the plugin's
5-
# MCP server is launched via uvx and `deepwork` is not on PATH) still work.
2+
# deepschema_write.sh - DeepSchema write hook
3+
#
4+
# Registered as a PostToolUse hook on Write/Edit in hooks.json.
5+
# Delegates to the `deepwork hook deepschema_write` Python entry point,
6+
# which validates the written file against applicable DeepSchemas.
7+
#
8+
# Always invokes via `uvx deepwork` to match the MCP server invocation
9+
# in plugins/claude/.mcp.json. This avoids a class of PATH-staleness
10+
# bugs where a user-level `deepwork` binary (e.g., `uv tool install
11+
# deepwork`) is older than the hook module it is being asked to run,
12+
# producing "Hook '...' not found" errors on every tool use.
13+
#
14+
# Input (stdin): JSON from Claude Code PostToolUse hook
15+
# Output (stdout): JSON response for Claude Code (allow/block + context)
16+
# Exit codes:
17+
# 0 on success, non-zero if uvx/the hook crashes (Claude Code
18+
# surfaces non-zero as a failed PostToolUse hook)
19+
20+
set -euo pipefail
621

722
INPUT=$(cat)
823
export DEEPWORK_HOOK_PLATFORM="claude"
9-
if command -v deepwork >/dev/null 2>&1; then
10-
echo "${INPUT}" | deepwork hook deepschema_write
11-
else
12-
echo "${INPUT}" | uvx deepwork hook deepschema_write
13-
fi
14-
exit $?
24+
echo "${INPUT}" | uvx deepwork hook deepschema_write
Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
#!/usr/bin/env bash
2-
# Post-commit reminder hook — delegates to deepwork Python hook.
3-
# Falls back to `uvx deepwork` so end-user installs (where the plugin's
4-
# MCP server is launched via uvx and `deepwork` is not on PATH) still work.
2+
# post_commit_reminder.sh - Post-commit review reminder hook
3+
#
4+
# Registered as a PostToolUse hook on Bash in hooks.json.
5+
# Delegates to the `deepwork hook post_commit_reminder` Python entry
6+
# point, which inspects the Bash command and (when it is a `git commit`)
7+
# nudges the agent to run the `review` skill if matching review rules
8+
# have not been marked as passed for the committed files.
9+
#
10+
# Always invokes via `uvx deepwork` to match the MCP server invocation
11+
# in plugins/claude/.mcp.json. This avoids a class of PATH-staleness
12+
# bugs where a user-level `deepwork` binary (e.g., `uv tool install
13+
# deepwork`) is older than the hook module it is being asked to run,
14+
# producing "Hook '...' not found" errors on every Bash tool use.
15+
#
16+
# Input (stdin): JSON from Claude Code PostToolUse hook
17+
# Output (stdout): JSON response for Claude Code (additionalContext for
18+
# the agent, or empty {} when nothing to add)
19+
# Exit codes:
20+
# 0 on success, non-zero if uvx/the hook crashes (Claude Code
21+
# surfaces non-zero as a failed PostToolUse hook)
22+
23+
set -euo pipefail
24+
525
INPUT=$(cat)
626
export DEEPWORK_HOOK_PLATFORM="claude"
7-
if command -v deepwork >/dev/null 2>&1; then
8-
echo "${INPUT}" | deepwork hook post_commit_reminder
9-
else
10-
echo "${INPUT}" | uvx deepwork hook post_commit_reminder
11-
fi
12-
exit $?
27+
echo "${INPUT}" | uvx deepwork hook post_commit_reminder

plugins/claude/hooks/post_compact.sh

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,13 @@ if [ -z "$CWD" ]; then
2222
fi
2323

2424
# ==== Fetch active sessions ====
25-
# Fall back to `uvx deepwork` so end-user installs (where the plugin's
26-
# MCP server is launched via uvx and `deepwork` is not on PATH) still work.
27-
if command -v deepwork >/dev/null 2>&1; then
28-
STACK_JSON=$(deepwork jobs get-stack --path "$CWD" 2>/dev/null) || {
29-
echo '{}'
30-
exit 0
31-
}
32-
else
33-
STACK_JSON=$(uvx deepwork jobs get-stack --path "$CWD" 2>/dev/null) || {
34-
echo '{}'
35-
exit 0
36-
}
37-
fi
25+
# Always invoke via `uvx deepwork` to match the MCP server invocation in
26+
# plugins/claude/.mcp.json — avoids PATH-staleness where a user-level
27+
# `deepwork` binary is older than the subcommand it is being asked to run.
28+
STACK_JSON=$(uvx deepwork jobs get-stack --path "$CWD" 2>/dev/null) || {
29+
echo '{}'
30+
exit 0
31+
}
3832

3933
# ==== Check for active sessions ====
4034
SESSION_COUNT=$(echo "$STACK_JSON" | jq '(.active_sessions // []) | length')

0 commit comments

Comments
 (0)