Skip to content

fix(claude-plugin): replace agent-type stop hook with command-type block decision#213

Merged
visahak merged 7 commits into
AgentToolkit:mainfrom
vinodmut:claudeautolearn
Apr 24, 2026
Merged

fix(claude-plugin): replace agent-type stop hook with command-type block decision#213
visahak merged 7 commits into
AgentToolkit:mainfrom
vinodmut:claudeautolearn

Conversation

@vinodmut

@vinodmut vinodmut commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace the agent-type Stop hook with a command-type hook that returns a block decision, fixing the learn skill not running at session end
  • The agent-type hook spawned a sub-agent without conversation context, so the learn skill had nothing to analyze
  • Add on_stop.sh script with recursion guard and block decision JSON

Closes #212

Test plan

  • Verify the stop hook fires at session end and triggers the learn skill
  • Verify the stop_hook_active guard prevents recursion on block-decision continuation
  • Verify suppressOutput and statusMessage produce clean UX

Summary by CodeRabbit

  • Improvements

    • Stop hook simplified to run a direct command that checks a runtime "stop" flag and exits silently when set.
    • If not stopped, the hook now returns a blocked response with suppressed output and an explicit instruction to run the learn workflow.
  • Tests

    • Plugin tests updated to accept hook command scripts in either Python or shell formats.

…k decision

The agent-type stop hook spawned a sub-agent without conversation context,
so the learn skill had nothing to analyze. Switch to a command-type hook
that returns a block decision, keeping execution in the parent agent which
has the full conversation history.
@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@vinodmut has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 32 minutes and 20 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 32 minutes and 20 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 124ea759-8fa6-47fc-a868-e41da3292691

📥 Commits

Reviewing files that changed from the base of the PR and between 8c50a86 and 9482bab.

📒 Files selected for processing (1)
  • platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py
📝 Walkthrough

Walkthrough

Replaces the Stop hook's agent-based execution with a command that runs a new Python (and companion shell) stop script which reads JSON stdin, checks stop_hook_active, and either exits silently or returns JSON instructing the system to block and invoke the /evolve-lite:learn skill.

Changes

Cohort / File(s) Summary
Hook Configuration
platform-integrations/claude/plugins/evolve-lite/hooks/hooks.json
Changed hooks.Stop[0] from type: "agent" with prompt and timeout to type: "command" invoking the stop script (now supports direct command execution).
Stop Hook Scripts
platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py, platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.sh
Added Python and shell stop-hook scripts that parse JSON stdin, exit silently if stop_hook_active is true, otherwise emit a JSON decision to block, suppressOutput, and direct the workflow to run /evolve-lite:learn.
Tests
tests/platform_integrations/test_plugin_structure.py
Updated test test_command_hook_scripts_exist to accept hook command script tokens ending with either .py or .sh and assert existence of the resolved script path.

Sequence Diagram(s)

sequenceDiagram
  participant Hook as Stop Hook (hooks.json)
  participant Runner as Command Runner
  participant Script as on_stop.py / on_stop.sh
  participant Skill as evolve-lite:learn

  Hook->>Runner: invoke configured command (stdin JSON)
  Runner->>Script: execute script with stdin
  Script->>Script: parse stdin JSON, check stop_hook_active
  alt stop_hook_active == true
    Script-->>Hook: exit silently (no output)
  else stop_hook_active != true
    Script-->>Hook: output JSON decision {action: "block", suppressOutput: true, systemMessage: "run /evolve-lite:learn", ...}
    Hook->>Skill: trigger /evolve-lite:learn per decision
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • visahak
  • illeatmyhat
  • gaodan-fang

Poem

🐰 I nibble stdin, sniff each flag with care,
If "true" lies within, I vanish—leave no air.
Else I tap the brakes, and whisper to the stream,
"Block and teach the learner"—hop, a tiny dream. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: replacing an agent-type stop hook with a command-type block decision, which is the core fix in this PR.
Linked Issues check ✅ Passed The PR successfully addresses all requirements from issue #212: replaces agent-type stop hook with command-type block decision, implements recursion guard via stop_hook_active, and provides full conversation context to the learn skill.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #212: hook configuration update, implementation of on_stop.py script, on_stop.sh as intermediate version, and test update to accept .sh scripts are all necessary for the fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vinodmut vinodmut changed the title fix(evolve-lite): replace agent-type stop hook with command-type block decision fix(claude-plugin): replace agent-type stop hook with command-type block decision Apr 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
platform-integrations/claude/plugins/evolve-lite/hooks/hooks.json (1)

30-31: Quote the expanded plugin-root script path.

If CLAUDE_PLUGIN_ROOT contains whitespace, Line 31 splits the path before bash receives it and the Stop hook will not run. Claude Code’s security guidance also recommends quoting shell variables: Claude Code hooks reference.

🛠️ Proposed fix
 {
   "type": "command",
-  "command": "bash ${CLAUDE_PLUGIN_ROOT}/skills/learn/scripts/on_stop.sh"
+  "command": "bash \"${CLAUDE_PLUGIN_ROOT}/skills/learn/scripts/on_stop.sh\""
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platform-integrations/claude/plugins/evolve-lite/hooks/hooks.json` around
lines 30 - 31, The Stop hook command string uses an unquoted environment
variable expansion ("bash
${CLAUDE_PLUGIN_ROOT}/skills/learn/scripts/on_stop.sh") which breaks if
CLAUDE_PLUGIN_ROOT contains whitespace; update the "command" value for this hook
to quote the expanded path so bash receives the full path (e.g., use bash with
the path wrapped in double quotes), preserving the same script path and keeping
the "command" JSON key and its value intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.sh`:
- Around line 10-15: The stop hook script on_stop.sh is emitting an invalid
field (statusMessage) in the hook output JSON; remove statusMessage from the
JSON returned by on_stop.sh so it only includes the allowed fields (decision,
reason, and optional continue) and keep suppressOutput only if supported, and
then add the spinner text as the hook configuration in hooks.json (attach the
statusMessage or equivalent display text to the stop hook entry in hooks.json).
Update on_stop.sh to output only decision and reason and move the
spinner/display text into hooks.json's stop hook configuration using the
statusMessage key there.

---

Nitpick comments:
In `@platform-integrations/claude/plugins/evolve-lite/hooks/hooks.json`:
- Around line 30-31: The Stop hook command string uses an unquoted environment
variable expansion ("bash
${CLAUDE_PLUGIN_ROOT}/skills/learn/scripts/on_stop.sh") which breaks if
CLAUDE_PLUGIN_ROOT contains whitespace; update the "command" value for this hook
to quote the expanded path so bash receives the full path (e.g., use bash with
the path wrapped in double quotes), preserving the same script path and keeping
the "command" JSON key and its value intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02dfae30-b45a-4967-a218-26c0a61901e5

📥 Commits

Reviewing files that changed from the base of the PR and between b670d92 and 2c1a32a.

📒 Files selected for processing (2)
  • platform-integrations/claude/plugins/evolve-lite/hooks/hooks.json
  • platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.sh

…decision

Addresses CodeRabbit review finding: statusMessage is not a valid
hook response field; replaced with systemMessage.
The test_command_hook_scripts_exist test only accepted .py scripts,
causing it to fail for the new on_stop.sh bash hook.
@vinodmut vinodmut requested review from illeatmyhat and visahak April 23, 2026 17:41
@visahak

visahak commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Code Review

  1. Unstated external dependency on jq

    • Reason: The new on_stop.sh script introduces a dependency on the jq binary to parse the JSON standard input. jq is not guaranteed to be installed on all user systems by default. If it is missing, the hook will fail with a "command not found" error and break the intended flow. Given that this project already relies on Python (and the test changes indicate prior hooks were exclusively .py scripts), it would be significantly more robust to rewrite this hook as a Python script using the standard json module. This guarantees cross-platform execution without requiring users to install additional system tools.
    • File: platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.sh:L3
  2. Incorrect key systemMessage vs statusMessage

    • Reason: The PR description explicitly mentions: "Verify suppressOutput and statusMessage produce clean UX". However, the actual JSON payload printed in on_stop.sh uses "systemMessage". A review of the codebase (such as install.sh and tests) confirms that "statusMessage" is the correct expected key for hook responses. This key mismatch will likely prevent the intended message from rendering.
    • File: platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.sh:L14

@visahak visahak left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added comments in the issue

… key

Addresses review feedback from visahak:
- Replace on_stop.sh (requires jq) with on_stop.py using stdlib json module,
  eliminating the unstated external dependency on jq
- Fix incorrect key "systemMessage" → "statusMessage" in hook JSON output

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py`:
- Around line 17-22: The JSON printed in on_stop.py uses the wrong output field:
replace the "statusMessage" key with "systemMessage" in the dict printed by the
print(json.dumps(...)) call so the message is shown to the user per the Claude
Code hooks contract; update the dict in the on_stop.py print block (the produced
JSON with "decision": "block", "reason": ..., "suppressOutput": True) to include
"systemMessage": "Running the evolve-lite learn skill..." instead of
"statusMessage".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27e4e14c-ddd7-4945-8d46-66a7602c0e41

📥 Commits

Reviewing files that changed from the base of the PR and between d58d250 and 8c50a86.

📒 Files selected for processing (3)
  • platform-integrations/claude/plugins/evolve-lite/hooks/hooks.json
  • platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py
  • tests/platform_integrations/test_plugin_structure.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • platform-integrations/claude/plugins/evolve-lite/hooks/hooks.json

Comment thread platform-integrations/claude/plugins/evolve-lite/skills/learn/scripts/on_stop.py Outdated
The hook output contract uses systemMessage (displayed to user),
not statusMessage (which is a hook config field in hooks.json).

Addresses CodeRabbit review finding: Change statusMessage to systemMessage
@vinodmut

Copy link
Copy Markdown
Contributor Author
  1. Incorrect key systemMessage vs statusMessage
    • Reason: The PR description explicitly mentions: "Verify suppressOutput and statusMessage produce clean UX". However, the actual JSON payload printed in on_stop.sh uses "systemMessage". A review of the codebase (such as install.sh and tests) confirms that "statusMessage" is the correct expected key for hook responses.

Good catch on the jq dependency — rewrote on_stop.sh as on_stop.py in 8c50a86.

On the key name though: systemMessage is actually the correct field for hook output JSON (what the script prints to stdout). The Claude Code hooks docs list it as a universal output field:

systemMessage — Warning message shown to the user

The statusMessage you found in install.sh is used in hook configuration entries (the objects inside hooks.json), which is a different context — that's the spinner text shown while the hook runs, not a field in the hook's JSON response.

Fixed in 9482bab — reverted back to systemMessage in on_stop.py.

@visahak could you check if the latest commits address your review?

@visahak visahak self-requested a review April 24, 2026 20:35
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.

Claude Code plugin: Stop hook agent-type learn skill doesn't actually run

2 participants