Autorun maintainer
Skill ahundt/autorun/plugins/autorun/skills/autorun-maintainer
Claude Code & codex plugin + Gemini cli extension to keep ai running, keep data safe, plan better, and keep running until tasks are done.
npx -y skills add ahundt/autorun --skill autorun-maintainerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 12 stars12 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Expertise in maintaining, debugging, and deploying the autorun hook system across Claude Code, Codex CLI, Gemini-family CLIs, Google Antigravity, Qwen Code, ForgeCode, custom harnesses, and desktop app integrations. 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.
SKILL.md
20.8 KB, as published. Nobody here has run it
Autorun Maintainer Skill: The Definitive Guide
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) without breaking active sessions on other harnesses.
1. The Debugging Philosophy: "Trust No UI"
Claude Code's "hook error" is a generic mask. Never trust the UI. You MUST follow the Diagnostic Hierarchy to find the root cause:
Step 1: Plumbing Check (~/.autorun/hook_entry_debug.log)
- Binary Selection: Verify
get_autorun_bin()found the correct venv. - Exit Codes: Did the CLI exit with
0(Allow/Ask) or2(Blocking Workaround)? - Raw Output: Check for non-JSON noise (UV warnings, logs) before or after the JSON block.
- Validation: Did
extract_json()isolate exactly one valid block viajson.loads?
Step 2: Logic Check (~/.autorun/daemon.log)
- FullPayload: Check
FullPayload. Are expected keys present (e.g.,_pid,_cwd)? - Timing: Check
DAEMON PROCESSING END. If duration > 9000ms, it will trigger a Claude timeout. - Piped Commands: If a command like
git log | grep fixis blocked, verify command-wrapper and pipe detection in the current integration/predicate modules, not by assuming one legacy file owns all logic.
Step 3: Source Check (~/.autorun/daemon_startup.log)
- Stale Code: Is the daemon loading from the intended source tree, plugin cache, or editable UV tool?
- Identity: Confirm the Commit Hash, source directory, and PID change when a restart is intentionally requested.
2. Platform Schema Deep Dive (Claude v2.1.41)
Claude Code performs strict JSON validation. A single extra field in a lifecycle event causes a silent failure.
The "Hook Error" Matrix
| 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. |
The "Ask" vs "Deny" Strategy
- The Conflict: Claude Code ignores
permissionDecision: "deny"at exit 0. - The Resolution:
- For AI-only feedback, use Exit 2 + Stderr (Bug #4669).
- For User-facing redirection (e.g., "Use trash instead of rm"), use
decision: "ask". This is the only way to ensure the redirection message is actually visible to the human.
- Gemini Symmetry: Always map
ask->denyfor Gemini incore.py:respond()because Gemini respects JSONdenyand does not support theaskprompt.
3. Deployment & Synchronization Architecture
The "Many-Location Bug" (Legacy)
Historically, fixes failed because the code was copied into many separate locations. Current installs must preserve a single source of truth wherever a harness supports it:
- UV Tool:
uv tool install --editable . - Claude Code: plugin cache and command files must point at the intended source or release artifact.
- Codex CLI: user hooks live in
~/.codex/hooks.json; plugin bundles may also exist, but duplicate hook sources must be explicitly configured. - Gemini-family CLIs: Gemini, Google Antigravity, and Qwen Code use extension/plugin surfaces that should link or copy the same
arextension layout. Antigravity installs should prefer the staged nativeagy plugin installbundle and fall back toagy plugin import geminionly when validation or install fails. - Custom harnesses: use
--custom-harness SPEConly for a harness flavored like an existing supported target. - Result: Edits in
src/reflect only after the relevant editable install, plugin cache, and daemon lifecycle have all been validated.
The "Stale Code Trap"
Source edits in src/ are IGNORED by the persistent daemon until the scoped daemon for that source tree has restarted. NEVER assume code is active just because you saved the file.
The Safe Local Install Flow
uv run --project plugins/autorun python -m autorun --install-dry-run
uv run --project plugins/autorun python -m autorun --install --force
cd plugins/autorun && uv tool install --force --editable . && cd ../..
autorun --status
autorun --restart-daemon
Use autorun --restart-daemon for the current install/source tree. Use autorun --restart-all-daemons only with explicit current-turn user approval because it can interrupt unrelated active sessions or worktree tests.
Status and Custom Harness Checks
- General status:
autorun --status - Dry-run install preview:
autorun --install-dry-run - Custom harness status:
autorun --status --custom-harness SPEC - Custom harness install:
autorun --install --custom-harness SPEC - SPEC grammar:
name=flavor:binary:config_dir[::display];::displayis the unambiguous optional display-name separator andconfig_dirmay contain literal:characters - Supported flavors:
gemini,qwen,antigravity,agy(alias forantigravity), andcodex
Critical Installer Fixes:
- Invisible Variable: For local marketplaces, Claude fails to substitute
${CLAUDE_PLUGIN_ROOT}.install.pyMUST manually substitute this in the~/.claude/plugins/cache/directory. - Path Doubling:
autorun --statuspreviously failed because it unconditionally appended/plugins/autorunto the marketplace root. Discovery must be idempotent. - Hook Source Ambiguity: Codex user hooks and plugin-bundled hooks can coexist only when install metadata explicitly says that is intended; otherwise status should report duplicate autorun hook sources as a problem.
- Custom Harness Identity: A custom harness must carry the hook identity of its flavor (
--cli codex,--cli antigravity, etc.) so autorun emits the correct response schema.
4. Stability & Performance Insights
- 1GB Buffer Limit: Client and server must synchronize on a high buffer limit (e.g., 1GB). Large session transcripts (500MB+) will crash the hook with
asyncio.LimitOverrunErrorif left at default (64KB). - Session ID Fallback: If
CLAUDE_SESSION_IDis missing,core.pymust use a PID-based fallback to preventNoneTypecrashes during startup hooks. - Socket Polling:
restart_daemon.pymust useis_daemon_responding()socket checks rather thantime.sleep(). Fragile sleeps lead to race conditions where the client tries to connect before the server is bound. - Plan Recovery:
plan_export.pyuses a "Fresh Context" workaround (Option 1). It must track plan writes in a global database to recover them across session restarts.
5. UI/UX: Formatting & Anti-Duplication
- Avoid Double-Escaping: Never call
json.dumpson strings that will be put into a dict. This causes literal\nin the UI. Pass raw strings; let the finalprint(json.dumps())handle encoding. - Anti-Reversion Warning: Beware of context "compaction." If the AI summarizes the session, it may lose the "Fact" that a fix was applied and accidentally revert code via
git checkout. Always verify the disk state after compaction.
6. Official & Internal References
- Claude Hooks Reference: https://code.claude.com/docs/en/hooks
- Claude Schema Output: https://code.claude.com/docs/en/hooks#json-output
- Gemini Hooks Reference: https://geminicli.com/docs/hooks/reference/
- Claude Bug #4669 (Exit 2): https://claude.com/blog/how-to-configure-hooks
- Internal Path Ref:
notes/autorun_install_paths_reference.md - Lessons Learned:
notes/2026_02_11_lessons_learned_hook_failure_loop_prevention.md
7. Mandatory Verification Checklist
Before declaring a task "Complete," you MUST:
- Schema Test:
echo '{"hook_event_name":"PreToolUse", "tool_name":"Bash", "tool_input":{"command":"rm test"}}' | autorun - Metadata Test:
autorun --version(Verify commit matches current git). - Restart Test: Confirm PID in
~/.autorun/daemon.lockhas changed. - Path Test: Verify the installed Claude plugin cache
hooks/hooks.jsonfor the current autorun version does NOT contain${CLAUDE_PLUGIN_ROOT}. - Pipes Test:
cargo build 2>&1 | head -50(Should be ALLOWED). - Status Test:
autorun --status(Ensure paths aren't doubled and all configured harnesses report). - Custom Harness Test: If custom targets are in scope, run
autorun --status --custom-harness SPECfor each configured custom harness.
8. Detailed Architectural Inventory
If synchronization fails, verify these locations for stale code:
- Git Source:
plugins/autorun/src/autorun/ - Dev Venv:
plugins/autorun/.venv/lib/python*/site-packages/autorun/ - Build Artifacts:
plugins/autorun/build/(remove only if it is known to be generated and unrelated to active work) - Claude Cache:
~/.claude/plugins/cache/autorun/autorun/<version>/ - UV Tool:
~/.local/share/uv/tools/autorun/(Must be editable) - Gemini Extension:
~/.gemini/extensions/ar/(Must be symlink) - Gemini Venv:
~/.gemini/extensions/ar/.venv/ - Codex User Hooks:
~/.codex/hooks.json - Codex Plugin Cache:
~/.codex/plugins/cache/personal/autorun/<version>/ - Antigravity Extension:
~/.gemini/antigravity-cli/extensions/ar/or the configured custom Antigravity root. - Qwen Extension:
~/.qwen/extensions/ar/ - Custom Harness Root: the
config_dirfromname=flavor:binary:config_dir[::display]; the optional display name follows::
9. Loop Detection Checklist
You are in a "Failure Loop" if:
- Tests Pass, Hooks Fail: Unit tests use source directly; hooks use stale binaries.
- "Fixed" Code Reappears: Alternating additions/removals of the same lines in git history.
- Multiple Daemons: multiple
autorun.daemonprocesses exist; distinguish current source-tree daemons from unrelated worktree/test daemons before restarting. - User Reports Broken rm: Safety guards appear inactive despite "Fix" commits.
10. Common Technical Pitfalls
- Stdin Consumption: Never read
sys.stdininsidetry_cli(). Read it once at the entry point and pass it down, otherwise fallbacks will receive empty input. - UV Warnings: Using deprecated fields like
tool.uv.default-extrasinpyproject.tomlcauses warnings onstderr. Claude Code treats this as a hook error. - PID Management: Prefer
autorun --restart-daemonfor the current source tree. Use broader daemon cleanup only when scoped restart cannot recover and the user has approved interrupting other active sessions. - Bytecode Cache:
__pycache__can persist stale logic. The restart script must purge these explicitly.
11. Testing Strategy (Triple-Layer)
- Unit (integrations.py): Test predicate logic (e.g.,
_not_in_pipe). - Integration (main.py): Test
should_block_command()with real predicates. - E2E (hook_entry.py): Test the full subprocess execution path with fake JSON payloads.
Synthetic Verification Examples:
# 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
12. Daemon Architecture & Lifecycle
The daemon is the high-performance "Brain" of autorun. It minimizes hook latency to 1-5ms.
Core Components:
- Unix Domain Socket (
~/.autorun/daemon.sock): High-speed communication path. Bypasses the overhead of TCP/IP. - Shared Magic State (
shelve): Persistent key-value store. Allows hooks to share state (e.g.,autorun_stage) across multiple independent subprocess invocations. - Watchdog Mechanism: The daemon monitors parent PIDs. If the spawning CLI dies, the daemon self-terminates after an idle timeout (30min) to prevent resource leakage.
- Tri-Layer Session Identity:
- Layer 1: harness session environment such as
CLAUDE_SESSION_ID,GEMINI_SESSION_ID,CODEX_SESSION_ID,AGY_SESSION_ID, orQWEN_SESSION_ID. - Layer 2: Parent PID fallback (If env var is lost).
- Layer 3: Current Working Directory fallback.
- Layer 1: harness session environment such as
Critical Daemon Gotchas:
- Socket Binding: If the
.sockfile exists but no process is running,client.pywill fail to connect. The restart script MUST clean up stale socket files. - Zombie Daemons: Multiple daemons running from different code versions will cause non-deterministic hook behavior. One might allow
rmwhile another blocks it. Always audit withpgrep. - Blocking vs. Non-Blocking IO: The daemon uses
asyncio. Any synchronoustime.sleep()or blocking subprocess call in a hook handler will freeze ALL hooks for ALL active sessions.
13. Full Hook Repair & Connectivity Guide
If hooks fail to connect or present errors, follow this repair guide.
Connectivity Failure Matrix
| 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` |
| "Hook Error" (UI) | Stderr noise or bad JSON. | tail -n 20 ~/.autorun/hook_entry_debug.log | Check for double-printing or UV warnings. |
The "Silent Fail-Open" Trap
Claude Code fails OPEN. If a hook script crashes, the tool (e.g., rm) will execute without warning.
- Verification: If
rmdoesn't block, checkhook_entry_debug.log. If it's empty, the script didn't even start (path issue).
Connectivity Specs:
- Protocol: JSON-over-STDIN (In), JSON-over-STDOUT (Out).
- Socket Type:
AF_UNIX(Unix Domain Socket). - Default Timeout: 10 seconds (Claude), 5 seconds (Gemini).
- Buffer Limit: 1GB (Synchronized in
client.pyandcore.py).
Reference Guide for Repairs:
- Official Hook Specs: https://code.claude.com/docs/en/hooks
- Claude JSON Output Ref: https://code.claude.com/docs/en/hooks#json-output
- Gemini Hook Reference: https://geminicli.com/docs/hooks/reference/
- Asyncio Stream Ref: https://docs.python.org/3/library/asyncio-stream.html
14. Deep Dive: Solving the "Hook Error" Loop
The "Hook Error" was the most persistent failure mode. It manifests as a generic UI message but represents three distinct layers of failure.
Layer 1: The Schema Violation ("Invalid Input")
Claude Code's JSON validator is event-specific. A field valid for one event will crash another.
- Symptom:
Stop: hook error: JSON validation failed: - : Invalid input - The Trap: Sending
decisionorreasonin a lifecycle event. - The Schema Source of Truth:
- PreToolUse: MUST have
permissionDecisionat root AND inhookSpecificOutput. Top-leveldecisionmust be"approve"or"block". - UserPromptSubmit / PostToolUse: MUST have
additionalContextinhookSpecificOutput. - Stop / SessionStart: MUST NOT have
decision,reason, orhookSpecificOutput.
- PreToolUse: MUST have
- Solution: The
validate_hook_response()method incore.pyacts as a strict whitelist filter per event type.
Layer 2: The Plumbing Noise ("Double-Printing")
Any non-JSON output on stdout causes a parsing error.
- Symptom:
Hook JSON output validation failed: Unexpected token '{' at position 120 - The Trap:
- Double JSON:
client.pyprints JSON, thenhook_entry.pyprints it again. - UV Noise:
uv runprinting "warning: tool.uv.default-extras is deprecated". - Logs: Stray
print("Debug: ...")in the source code.
- Double JSON:
- Solution:
- Refactor
hook_entry.pyto useextract_json()which finds exactly one{...}block usingjson.loadsvalidation. - Use
logger.info(file-only) instead ofprintfor all internal status messages.
- Refactor
Layer 3: The Execution Gap ("No such file")
The hook script is registered but cannot be found or executed.
- Symptom:
Stop hook error: can't open file '${CLAUDE_PLUGIN_ROOT}/hooks/hook_entry.py': [Errno 2] No such file or directory - The Trap:
- Missing Substitution: Claude fails to replace
${CLAUDE_PLUGIN_ROOT}for local marketplaces. - Partial Install:
hooks/directory skipped duringshutil.copytreedue to path logic.
- Missing Substitution: Claude fails to replace
- Solution:
install.pymust manuallysed-replace the variables in~/.claude/plugins/cache/.- Verify existence with:
ls -l ~/.claude/plugins/cache/autorun/autorun/<version>/hooks/hook_entry.py.
Layer 4: The Silent Ignore (Bug #4669)
The hook "succeeds" (exit 0) but the safety guard is ignored.
- Symptom:
rmcommand prompts for "remove file?" instead of being blocked. - The Trap: Claude Code ignores
permissionDecision: "deny"if the process exits with code 0. - Solution: The Exit 2 Workaround. You MUST print the reason to
stderrandsys.exit(2)to trigger an actual block that the AI sees.
15. Stream Protocol & Stderr/Stdout Sensitivity
Claude Code interprets stdout and stderr differently based on the exit code. Mismanaging these streams is the primary cause of "Hook Errors."
The 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.
The "Exactly One JSON" Rule (stdout)
Claude's parser is fragile. If stdout contains anything other than a single valid JSON block, it fails.
- The Problem:
uv runwarnings, daemon status logs, or multipleprint(json.dumps())calls. - The Fix:
hook_entry.pymust use a robust extractor:- Capture all
stdout. - Use a sliding window or regex to find the last
{...}block. - Validate with
json.loads(). - Print only that block and exit.
- Capture all
UI Clutter: The Triple-Print & Double-Escape
- Triple-Print: Claude displays three fields simultaneously:
systemMessage,hookSpecificOutput.permissionDecisionReason, andstderr(at exit 2).- Solution: For
denydecisions, empty the top-level fields incore.py:respond()to show only one clean message.
- Solution: For
- Double-Escape: Occurs when you manually escape a string (e.g., replacing
\nwith\\n) and then pass it tojson.dumps().- Result: User sees literal
\ntext instead of newlines. - Solution: Always pass raw strings through the internal logic. Let the final
json.dumps()at the system boundary handle the encoding.
- Result: User sees literal