plugins/autorun/skills/autorun-maintainer/SKILL.md
Expertise in maintaining, debugging, and deploying the autorun hook system for Claude Code and Gemini CLI. Use when the user asks to "fix hooks", "deploy autorun", "debug hook errors", "update autorun version", or when troubleshooting "invisible failures" where safety guards appear inactive, piped commands are blocked, or work appears to have "reverted" after a session.
npx skillsauth add ahundt/autorun autorun-maintainerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a Senior QA and Release Engineer specialized in the autorun hook ecosystem. Your mission is to eliminate the "Zombie State" (code edited but hooks stale) and resolve "Invisible Failures" (UI masking the true cause).
Claude Code's "hook error" is a generic mask. Never trust the UI. You MUST follow the Diagnostic Hierarchy to find the root cause:
~/.autorun/hook_entry_debug.log)get_autorun_bin() found the correct venv.0 (Allow/Ask) or 2 (Blocking Workaround)?extract_json() isolate exactly one valid block via json.loads?~/.autorun/daemon.log)FullPayload. Are expected keys present (e.g., _pid, _cwd)?DAEMON PROCESSING END. If duration > 9000ms, it will trigger a Claude timeout.git log | grep fix is blocked, verify the _not_in_pipe predicate is registered in main.py:_PREDICATES.~/.autorun/daemon_startup.log).../cache/... (STALE) or .../plugins/autorun/src/... (FRESH)?Claude Code performs strict JSON validation. A single extra field in a lifecycle event causes a silent failure.
| Symptom | Event Type | Cause | Resolution |
| :--- | :--- | :--- | :--- |
| "Invalid Input" | Stop, SessionStart | Sent decision or reason. | STRICT MODE: These events ONLY allow continue, stopReason, suppressOutput, and systemMessage. |
| "Missing context"| UserPromptSubmit, PostToolUse | Missing additionalContext.| Map feedback to additionalContext inside hookSpecificOutput. |
| "JSON failed" | PreToolUse | Missing permissionDecision.| Must exist at top-level AND in hookSpecificOutput. |
| "Double print" | All | hook_entry.py printed noise. | Refactor hook_entry.py to isolate and print exactly one JSON block. |
permissionDecision: "deny" at exit 0.decision: "ask". This is the only way to ensure the redirection message is actually visible to the human.ask -> deny for Gemini in core.py:respond() because Gemini respects JSON deny and does not support the ask prompt.Historically, fixes failed because the code was copied into 9 separate locations. We now use Symlink Architecture:
uv tool install --editable .gemini extensions link /path/to/reposrc/ reflect immediately in those binaries.Source edits in src/ are IGNORED by the persistent daemon until autorun --restart-daemon is run. NEVER assume code is active just because you saved the file.
uv run --project plugins/autorun python -m autorun --install --force && \
cd plugins/autorun && uv tool install --force --editable . && cd ../.. && \
autorun --restart-daemon
${CLAUDE_PLUGIN_ROOT}. install.py MUST manually substitute this in the ~/.claude/plugins/cache/ directory.autorun --status previously failed because it unconditionally appended /plugins/autorun to the marketplace root. Discovery must be idempotent.asyncio.LimitOverrunError if left at default (64KB).CLAUDE_SESSION_ID is missing, core.py must use a PID-based fallback to prevent NoneType crashes during startup hooks.restart_daemon.py must use is_daemon_responding() socket checks rather than time.sleep(). Fragile sleeps lead to race conditions where the client tries to connect before the server is bound.plan_export.py uses a "Fresh Context" workaround (Option 1). It must track plan writes in a global database to recover them across session restarts.json.dumps on strings that will be put into a dict. This causes literal \n in the UI. Pass raw strings; let the final print(json.dumps()) handle encoding.git checkout. Always verify the disk state after compaction.notes/autorun_install_paths_reference.mdnotes/2026_02_11_lessons_learned_hook_failure_loop_prevention.mdBefore declaring a task "Complete," you MUST:
echo '{"hook_event_name":"PreToolUse", "tool_name":"Bash", "tool_input":{"command":"rm test"}}' | autorunautorun --version (Verify commit matches current git).~/.autorun/daemon.lock has changed.~/.claude/plugins/cache/autorun/autorun/0.11.0/hooks/hooks.json does NOT contain ${CLAUDE_PLUGIN_ROOT}.cargo build 2>&1 | head -50 (Should be ALLOWED).autorun --status (Ensure paths aren't doubled).If synchronization fails, verify these locations for stale code:
plugins/autorun/src/autorun/plugins/autorun/.venv/lib/python*/site-packages/autorun/plugins/autorun/build/ (DELETE THIS)~/.claude/plugins/cache/autorun/autorun/0.11.0/~/.local/share/uv/tools/autorun/ (Must be editable)~/.gemini/extensions/ar/ (Must be symlink)~/.gemini/extensions/ar/.venv/~/.gemini/extensions/pdf-extractor/~/.gemini/extensions/ar/build/ (DELETE THIS)You are in a "Failure Loop" if:
pgrep -f "autorun.daemon" | wc -l > 1.sys.stdin inside try_cli(). Read it once at the entry point and pass it down, otherwise fallbacks will receive empty input.tool.uv.default-extras in pyproject.toml causes warnings on stderr. Claude Code treats this as a hook error.pkill -f "autorun.daemon" after changes. Stale processes bind the socket and prevent new code from running.__pycache__ can persist stale logic. The restart script must purge these explicitly._not_in_pipe).should_block_command() with real predicates.# SessionStart
echo '{"hook_event_name":"SessionStart"}' | autorun
# PreToolUse (rm block)
echo '{"hook_event_name":"PreToolUse", "tool_name":"Bash", "tool_input":{"command":"rm test"}}' | autorun
# Piped Command (Allow check)
echo '{"hook_event_name":"PreToolUse", "tool_name":"Bash", "tool_input":{"command":"git log | grep fix"}}' | autorun
The daemon is the high-performance "Brain" of autorun. It minimizes hook latency to 1-5ms.
~/.autorun/daemon.sock): High-speed communication path. Bypasses the overhead of TCP/IP.shelve): Persistent key-value store. Allows hooks to share state (e.g., autorun_stage) across multiple independent subprocess invocations.CLAUDE_SESSION_ID / GEMINI_SESSION_ID (Direct)..sock file exists but no process is running, client.py will fail to connect. The restart script MUST clean up stale socket files.rm while another blocks it. Always audit with pgrep.asyncio. Any synchronous time.sleep() or blocking subprocess call in a hook handler will freeze ALL hooks for ALL active sessions.If hooks fail to connect or present errors, follow this repair guide.
| Symptom | Probable Cause | Diagnostic Command | Repair Action |
| :--- | :--- | :--- | :--- |
| "Connection Refused" | Daemon not running or socket stale. | ls -l ~/.autorun/daemon.* | Run autorun --restart-daemon. |
| "No such file" (Hook CLI)| ${CLAUDE_PLUGIN_ROOT} missing. | cat hooks/hook_entry_debug.log | Run autorun --install --force. |
| "ImportError" | Python deps missing in venv. | uv pip list --project plugins/autorun | Run uv sync --project plugins/autorun. |
| "Hang" (Claude wait) | Daemon frozen or buffer full. | ps aux | grep autorun.daemon | pkill -f daemon + autorun --restart-daemon. |
| "Hook Error" (UI) | Stderr noise or bad JSON. | tail -n 20 ~/.autorun/hook_entry_debug.log| Check for double-printing or UV warnings. |
Claude Code fails OPEN. If a hook script crashes, the tool (e.g., rm) will execute without warning.
rm doesn't block, check hook_entry_debug.log. If it's empty, the script didn't even start (path issue).AF_UNIX (Unix Domain Socket).client.py and core.py).The "Hook Error" was the most persistent failure mode. It manifests as a generic UI message but represents three distinct layers of failure.
Claude Code's JSON validator is event-specific. A field valid for one event will crash another.
Stop: hook error: JSON validation failed: - : Invalid inputdecision or reason in a lifecycle event.permissionDecision at root AND in hookSpecificOutput. Top-level decision must be "approve" or "block".additionalContext in hookSpecificOutput.decision, reason, or hookSpecificOutput.validate_hook_response() method in core.py acts as a strict whitelist filter per event type.Any non-JSON output on stdout causes a parsing error.
Hook JSON output validation failed: Unexpected token '{' at position 120client.py prints JSON, then hook_entry.py prints it again.uv run printing "warning: tool.uv.default-extras is deprecated".print("Debug: ...") in the source code.hook_entry.py to use extract_json() which finds exactly one {...} block using json.loads validation.logger.info (file-only) instead of print for all internal status messages.The hook script is registered but cannot be found or executed.
Stop hook error: can't open file '${CLAUDE_PLUGIN_ROOT}/hooks/hook_entry.py': [Errno 2] No such file or directory${CLAUDE_PLUGIN_ROOT} for local marketplaces.hooks/ directory skipped during shutil.copytree due to path logic.install.py must manually sed-replace the variables in ~/.claude/plugins/cache/.ls -l ~/.claude/plugins/cache/autorun/autorun/0.11.0/hooks/hook_entry.py.The hook "succeeds" (exit 0) but the safety guard is ignored.
rm command prompts for "remove file?" instead of being blocked.permissionDecision: "deny" if the process exits with code 0.stderr and sys.exit(2) to trigger an actual block that the AI sees.Claude Code interprets stdout and stderr differently based on the exit code. Mismanaging these streams is the primary cause of "Hook Errors."
stderr Sensitivity Rules| Exit Code | stderr Content | Claude Code Result |
| :--- | :--- | :--- |
| 0 (Success) | Any characters | FAILURE: Treated as "hook error". JSON is ignored. |
| 0 (Success) | Empty | SUCCESS: JSON is parsed and processed. |
| 2 (Block) | Reason string | SUCCESS: Tool blocked. Reason is fed to AI as feedback. |
| 2 (Block) | Empty | SUCCESS: Tool blocked. AI gets generic "Tool failed" message. |
Meta-Rule: NEVER use print() for logging in hook paths. Use a file-only logger (e.g., logging_utils.py) to keep stdout/stderr pristine.
stdout)Claude's parser is fragile. If stdout contains anything other than a single valid JSON block, it fails.
uv run warnings, daemon status logs, or multiple print(json.dumps()) calls.hook_entry.py must use a robust extractor:
stdout.{...} block.json.loads().systemMessage, hookSpecificOutput.permissionDecisionReason, and stderr (at exit 2).
deny decisions, empty the top-level fields in core.py:respond() to show only one clean message.\n with \\n) and then pass it to json.dumps().
\n text instead of newlines.json.dumps() at the system boundary handle the encoding.tools
Use this skill when the user asks to "enable cache protection", "block on cache miss", "avoid cache expiration cost", "/ar:cache", "protect against compaction", "cache pressure guard", "cache hit ratio threshold", or "set cache-miss threshold". Covers the /ar:cache slash command family for blocking tool use when the Claude Code prompt cache is cold or context is near compaction.
tools
(Renamed) Search, recover, and analyze sessions from Claude Code, AI Studio, and Gemini CLI — use /ar:ai-session-tools (this is an alias that redirects there).
tools
Search, recover, and analyze AI session histories across Claude Code, AI Studio, and Gemini CLI. Use when user asks to "find that file from last week", "search sessions", "recover context after compaction", "what did the AI do", "export session to markdown", "find corrections", "analyze session quality", "improve CLAUDE.md from past mistakes", or "turn AI mistakes into rules". Contains session search, file recovery, correction detection, self-improvement workflow.
development
This skill should be used when the user asks to "extract text from PDF", "convert PDF to text", "parse PDF", "read PDF contents", "extract data from documents", "batch PDF extraction", "PDF to markdown", "OCR PDF", "get text from PDF files", "I have a PDF", "can you read this PDF", "what's in this PDF", "summarize this PDF", "open PDF file", "extract from [filename].pdf", or needs to process PDF documents for data extraction. Handles single-file extraction, batch processing, and OCR for scanned documents with automatic backend selection.