AgentStack
SKILL verified MIT Self-run

Hook Creator

skill-tkellogg-open-strix-hook-creator · by tkellogg

Create and manage runtime command hooks declared in skills/*/hooks.json. Use when the user asks to intercept tool calls, augment prompts, audit or mutate tool arguments/results, add startup/shutdown scripts, or debug hook behavior.

No reviews yet
0 installs
13 views
0.0% view→install

Install

$ agentstack add skill-tkellogg-open-strix-hook-creator

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Hook Creator? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Hooks

Hooks are short-lived commands that receive one runtime event as JSON on stdin and may write one JSON object to stdout to replace it. They live in skills as hooks.json files and are discovered at startup and when reload_hooks is called.

Use hooks for small runtime adapters:

  • pre/post tool-call policy
  • prompt augmentation before the model is invoked
  • send_message shaping or audit
  • metrics and local logging
  • startup/shutdown setup and teardown

Do not use hooks for long-running services. Use UI plugins, pollers, supervisor jobs, or OS services for that.

hooks.json

{
  "hooks": [
    {
      "name": "message-policy",
      "command": "uv run python hook.py",
      "events": ["pre_tool_call", "post_tool_call", "pre_prompt"],
      "env": {},
      "timeout_seconds": 10,
      "include_conversation": false
    }
  ]
}

Valid events:

  • pre_tool_call
  • post_tool_call
  • pre_prompt
  • pre_startup
  • post_startup
  • pre_shutdown
  • post_shutdown

There are no tool-name filters in hooks.json. Filter explicitly in the hook script:

import json
import sys

event = json.loads(sys.stdin.readline())
if event.get("tool") != "send_message":
    print(json.dumps(event))
    raise SystemExit

event["args"]["text"] = event["args"]["text"].strip()
print(json.dumps(event))

Contract

The hook process runs from the directory containing hooks.json.

Environment:

  • STATE_DIR: the skill directory
  • HOOK_NAME: the hook name
  • HOOK_EVENT: the event type

Input:

  • exactly one JSON object plus a newline on stdin

Output:

  • stdout empty means no-op
  • stdout containing a JSON object replaces the event for the next hook
  • stderr is logged as hook_stderr

For pre_tool_call, returning an event with an args object changes the tool args before execution. For post_tool_call, returning an event with result changes the result shown to the agent after a successful tool call. For pre_prompt, returning prompt replaces the prompt, and returning append_prompt appends text to the prompt.

Blocking a tool call with _block

A pre_tool_call hook may reject the call entirely by returning an event with an _block object:

import json
import sys

event = json.loads(sys.stdin.readline())
if event.get("tool") == "send_message" and looks_bad(event["args"]):
    event["_block"] = {
        "reason": "shell-leak-prefix",
        "result": "[hook blocked] send_message text starts with `$(...)` — that ships as a literal string, not a shell command. Read the file and inline the contents, or attach via attachment_paths.",
    }
print(json.dumps(event))

When _block is set with both reason (string) and result (string), the wrapped tool is not invoked. The agent receives result as if it were the tool's own return value — so the agent sees the diagnostic next turn and can self-correct. A hook_blocked_tool_call event is logged, and posttoolcall hooks see status="blocked" with the same synthetic result.

This is the right verb when a hook needs to prevent a bad call rather than mutate it. Args-mutation is fine when the call should still happen but with different inputs; _block is for "this call must not happen — tell the agent why."

Invalid _block specs (missing fields, wrong types) are logged via hook_invalid_block and the call proceeds normally. This guards against silent drops from malformed hook output.

Set include_conversation: true only for hooks that need transcript context. The hook event then includes:

  • conversation.all_messages
  • conversation.channel_messages
  • conversation.current_channel_id

When logs/chat-history.jsonl exists, this is loaded from the persisted chat transcript. This is intentionally opt-in because full transcripts can be large.

Reload

After creating or editing hooks.json, call:

reload_hooks()

Debugging

Check logs/events.jsonl for:

  • hook_invalid_json
  • hook_invalid_format
  • hook_missing_fields
  • hook_timeout
  • hook_exec_error
  • hook_stderr
  • hook_nonzero_exit
  • hook_blocked_tool_call
  • hook_invalid_block
  • hook_invalid_mutation
  • hook_invalid_output
  • hook_invalid_mutation

Keep hooks small and deterministic. Short scripts are easier to reason about than daemons hiding in the tool path.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.