Testing Hooks Locally
dq-awesomeqa's three hooks can be validated entirely offline — no Claude Code session needed. This page covers how to test each hook in isolation and how to confirm the safety hook fires correctly inside a live session.
Prerequisites
- Node.js ≥ 18 (runs the safety and audit hooks)
jqrecommended for cleaner JSON output (brew install jq)- The plugin repo cloned locally at a known path (e.g.
~/dq-awesomeqa)
Testing hooks in isolation
Pipe JSON payloads directly to each hook script and inspect the exit code. No Claude session is required.
Safety hook (qa-safety.js)
The hook exits 0 (allow) or 2 (block). Test both cases to confirm the guard is working correctly.
Should ALLOW — safe YAML file write:
echo '{
"tool_name": "Write",
"tool_input": {
"file_path": "./load-tests/dq-nbomber.yaml",
"content": "version: 1"
}
}' | node hooks/qa-safety.js
echo "Exit code: $?" # expect: 0Should ALLOW — write inside /tmp:
echo '{
"tool_name": "Bash",
"tool_input": { "command": "rm -rf /tmp/test-run" }
}' | node hooks/qa-safety.js
echo "Exit code: $?" # expect: 0Should ALLOW — npx (not a bare package install):
echo '{
"tool_name": "Bash",
"tool_input": { "command": "npx playwright show-report ./qa-reports/ui" }
}' | node hooks/qa-safety.js
echo "Exit code: $?" # expect: 0Should BLOCK — editing a TypeScript source file:
echo '{
"tool_name": "Edit",
"tool_input": {
"file_path": "./src/app/auth.ts",
"old_string": "foo",
"new_string": "bar"
}
}' | node hooks/qa-safety.js
echo "Exit code: $?" # expect: 2Should BLOCK — sudo command:
echo '{
"tool_name": "Bash",
"tool_input": { "command": "sudo rm -rf /etc" }
}' | node hooks/qa-safety.js
echo "Exit code: $?" # expect: 2Should BLOCK — bare npm install during a QA run:
echo '{
"tool_name": "Bash",
"tool_input": { "command": "npm install lodash" }
}' | node hooks/qa-safety.js
echo "Exit code: $?" # expect: 2Session-start hook
Run the session-start hook directly. It should return valid JSON containing the skill index (lifecycle order, config contract, all skill entries).
CLAUDE_PLUGIN_ROOT=/path/to/dq-awesomeqa \
bash hooks/session-startVerify the JSON is well-formed:
CLAUDE_PLUGIN_ROOT=/path/to/dq-awesomeqa \
bash hooks/session-start | python3 -m json.toolExpected: parses without errors and hookSpecificOutput.additionalContext contains the skill index listing.
Stop hook
Test with a full usage payload:
echo '{"usage": {"input_tokens": 2100}}' | \
CLAUDE_PLUGIN_ROOT=/path/to/dq-awesomeqa \
bash hooks/stopExpected output:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "[dq-awesomeqa] Turn complete | tokens this turn: 2100"
}
}Test the fallback path (no jq / no token data):
echo '{}' | CLAUDE_PLUGIN_ROOT=/path/to/dq-awesomeqa bash hooks/stopExpected: falls back to "use /status to check token usage" in the context string.
Confirming the safety hook fires inside a live session
With the plugin installed, ask Claude to do something the hook should block:
Please edit the file ./src/app.ts to add a console.log at line 1The qa-safety.js hook intercepts the tool call before Claude acts. Claude should respond explaining it cannot modify application source files during a QA session.
What to look for
Claude's reply should reference its QA read-only role and decline to make the edit — not attempt a workaround. If Claude does attempt the edit, the hook is not firing; verify the plugin is installed (claude plugin list) and the hook is declared in hooks/hooks.json.
Quick reference
| Task | Command |
|---|---|
| Test safety hook (allow) | echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | node hooks/qa-safety.js |
| Test safety hook (block) | echo '{"tool_name":"Edit","tool_input":{"file_path":"app.ts","old_string":"x","new_string":"y"}}' | node hooks/qa-safety.js |
| Test session-start hook | CLAUDE_PLUGIN_ROOT=$(pwd) bash hooks/session-start |
| Validate session-start JSON | CLAUDE_PLUGIN_ROOT=$(pwd) bash hooks/session-start | python3 -m json.tool |
| Test stop hook | echo '{"usage":{"input_tokens":1234}}' | CLAUDE_PLUGIN_ROOT=$(pwd) bash hooks/stop |
