Model Citizen by Jake Selby v0.13.1
GitHub
HookPreToolUse

plan-webfetch

PreToolUse hook: approve WebFetch while in plan mode, so gathering context for a plan does not prompt for every documentation or reference URL.

Event
PreToolUse
Runs
always
Dispatcher
adapters/claude-code/hook.py
Script
policy/hooks/allow-plan-webfetch.py

What it does

Scope is deliberately narrow: only when permission_mode is plan, and only for http/https URLs. Every other mode keeps its normal permission flow, so this changes nothing outside planning. This hook never denies.

Why this is safe enough to allow: plan mode already blocks edits, so the fetch cannot change anything, and the neutralize-tool-output hook still scans the fetched text for instruction-shaped content before it reaches the model. It does not make fetched pages trusted; it removes the prompt for read-only research.

Test: echo '{"tool_name":"WebFetch","permission_mode":"plan","tool_input":{"url":"https://example.com"}}' | python3 allow-plan-webfetch.py

When it runs

The policy answers on PreToolUse. It runs in every install. It has no settings entry of its own: harness sync registers one dispatcher command per event, adapters/claude-code/hook.py, marked harness:runtime-pretooluse in your settings.json. That dispatcher routes each call to the policies its event and tool need and composes their answers; plan-webfetch is the policy's id in OWNERSHIP.json.

The script

policy/hooks/allow-plan-webfetch.pypython
#!/usr/bin/env python3
"""PreToolUse hook: approve WebFetch while in plan mode, so gathering context for a
plan does not prompt for every documentation or reference URL.

Scope is deliberately narrow: only when `permission_mode` is `plan`, and only for
`http`/`https` URLs. Every other mode keeps its normal permission flow, so this
changes nothing outside planning. This hook never denies.

Why this is safe enough to allow: plan mode already blocks edits, so the fetch
cannot change anything, and the `neutralize-tool-output` hook still scans the
fetched text for instruction-shaped content before it reaches the model. It does
not make fetched pages trusted; it removes the prompt for read-only research.

Test: echo '{"tool_name":"WebFetch","permission_mode":"plan","tool_input":{"url":"https://example.com"}}' | python3 allow-plan-webfetch.py
"""
import json
import sys


def main():
    try:
        payload = json.load(sys.stdin)
    except Exception:
        return
    if payload.get("tool_name") != "WebFetch":
        return
    if payload.get("permission_mode") != "plan":
        return
    url = (payload.get("tool_input") or {}).get("url")
    if not isinstance(url, str):
        return
    if not (url.startswith("https://") or url.startswith("http://")):
        return
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "allow",
            "permissionDecisionReason": "plan-mode web research (allow-plan-webfetch hook)",
        }
    }))


if __name__ == "__main__":
    main()
↑↓ move↵ openPagefind · indexed at build