Install
$ agentstack add skill-tkellogg-open-strix-hook-creator ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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_callpost_tool_callpre_promptpre_startuppost_startuppre_shutdownpost_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 directoryHOOK_NAME: the hook nameHOOK_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_messagesconversation.channel_messagesconversation.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_jsonhook_invalid_formathook_missing_fieldshook_timeouthook_exec_errorhook_stderrhook_nonzero_exithook_blocked_tool_callhook_invalid_blockhook_invalid_mutationhook_invalid_outputhook_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.
- Author: tkellogg
- Source: tkellogg/open-strix
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.