Skip to content

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)
  • jq recommended 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:

bash
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: 0

Should ALLOW — write inside /tmp:

bash
echo '{
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf /tmp/test-run" }
}' | node hooks/qa-safety.js
echo "Exit code: $?"   # expect: 0

Should ALLOW — npx (not a bare package install):

bash
echo '{
  "tool_name": "Bash",
  "tool_input": { "command": "npx playwright show-report ./qa-reports/ui" }
}' | node hooks/qa-safety.js
echo "Exit code: $?"   # expect: 0

Should BLOCK — editing a TypeScript source file:

bash
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: 2

Should BLOCK — sudo command:

bash
echo '{
  "tool_name": "Bash",
  "tool_input": { "command": "sudo rm -rf /etc" }
}' | node hooks/qa-safety.js
echo "Exit code: $?"   # expect: 2

Should BLOCK — bare npm install during a QA run:

bash
echo '{
  "tool_name": "Bash",
  "tool_input": { "command": "npm install lodash" }
}' | node hooks/qa-safety.js
echo "Exit code: $?"   # expect: 2

Session-start hook

Run the session-start hook directly. It should return valid JSON containing the skill index (lifecycle order, config contract, all skill entries).

bash
CLAUDE_PLUGIN_ROOT=/path/to/dq-awesomeqa \
  bash hooks/session-start

Verify the JSON is well-formed:

bash
CLAUDE_PLUGIN_ROOT=/path/to/dq-awesomeqa \
  bash hooks/session-start | python3 -m json.tool

Expected: parses without errors and hookSpecificOutput.additionalContext contains the skill index listing.


Stop hook

Test with a full usage payload:

bash
echo '{"usage": {"input_tokens": 2100}}' | \
  CLAUDE_PLUGIN_ROOT=/path/to/dq-awesomeqa \
  bash hooks/stop

Expected output:

json
{
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "[dq-awesomeqa] Turn complete | tokens this turn: 2100"
  }
}

Test the fallback path (no jq / no token data):

bash
echo '{}' | CLAUDE_PLUGIN_ROOT=/path/to/dq-awesomeqa bash hooks/stop

Expected: 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 1

The 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

TaskCommand
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 hookCLAUDE_PLUGIN_ROOT=$(pwd) bash hooks/session-start
Validate session-start JSONCLAUDE_PLUGIN_ROOT=$(pwd) bash hooks/session-start | python3 -m json.tool
Test stop hookecho '{"usage":{"input_tokens":1234}}' | CLAUDE_PLUGIN_ROOT=$(pwd) bash hooks/stop

Released under the AGPL-3.0 License.