Three Claude Code hooks that turn Eyepup into an autonomous loop

Eduard CristeaFounder, Eyepup4 min read

Claude Code's hook system lets you run shell commands at every lifecycle event — pre-prompt, pre-tool-use, post-edit, stop. Three of those wire Eyepup's CLI into a closed loop without changing anything else in your setup. Here's the working config we use, plus what each hook actually does.

Why hooks, not MCP

The "right" way to wire Eyepup into Claude Code is an MCP server — and that's on the roadmap. Today, hooks get you 90% there with zero glue code. The model-side changes (new tool calls, manifest entries) are exactly zero; you're just running shell commands that pipe into / out of Claude Code's context.

If you've got eyepup login working and Claude Code installed, the three hooks below take ~10 minutes to wire.

Hook 1 — Pre-tool: surface the highest-impact pattern before any UX edit

Goal: when Claude Code is about to edit a UX-related file, intercept and remind it what the highest-impact friction pattern is. Prevents the agent shipping a copy edit when there's a bigger fix waiting.

.claude/hooks/pre-tool-use.yml:

match:
  tool: edit
  paths:
    - "**/*.tsx"
    - "**/*.jsx"
    - "**/*.html"
    - "app/**"
    - "src/components/**"
run: |
  echo "▼ Eyepup top friction patterns (consider before editing):"
  eyepup todo --limit 3 || echo "(eyepup unavailable, continuing)"
  echo "▲"

Output Claude Code sees right before its edit tool call:

▼ Eyepup top friction patterns (consider before editing):
1. Pricing Toggle Ambiguity (impact 12.0, 4 visitors)
   → Replace toggle with single annual/monthly card
2. Mobile Footer CTA Obscured (impact 9.0, 3 visitors)
   → Move CTA above the cookie banner on mobile
3. Insurance Coverage Hesitation (impact 6.0, 2 visitors)
   → Add an eligibility quick-check above the fold on /insurance
▲

The agent's prompt now contains this output. If you ask it to "edit /about", it'll often pause and ask whether the more impactful /pricing fix should go first. That's the hook earning its keep.

The || echo failsafe is critical — if eyepup isn't installed on a teammate's machine the hook doesn't break their flow.

Hook 2 — Post-edit: auto-log every UX change

Goal: when Claude Code edits a file, log the change to Eyepup so the dossier agent can reason about whether the next batch of visitors recovered.

.claude/hooks/post-tool-use.yml:

match:
  tool: edit
  paths:
    - "app/**"
    - "src/components/**"
    - "**/*.tsx"
run: |
  TITLE=$(git log -1 --pretty=%s 2>/dev/null || echo "Edit ${EDIT_FILE_PATH}")
  PATHS=$(git diff --name-only HEAD~1 2>/dev/null | tr '\n' ',' | sed 's/,$//')
  eyepup log "$TITLE" \
    --kind content_change \
    --paths "${PATHS:-/}" \
    --description "Shipped via Claude Code · file: ${EDIT_FILE_PATH}" \
    || true

What happens:

  1. Claude Code finishes an edit
  2. Hook reads the most recent git commit subject (or falls back to a generic title)
  3. Calls eyepup log with the kind, paths touched, and a description tagging Claude Code as the source
  4. Eyepup writes a changelog row that the dossier agent reads on its next pass

Why this matters: without the log, when new visitors arrive after the deploy, the LLM has no idea anything changed. With the log, it grades the new sessions against the change — "this visitor saw the post-edit version of /pricing and converted at 70% heat" — and either retires the friction pattern or flags it as unresolved.

The || true keeps a network blip on the eyepup endpoint from breaking the post-edit hook.

Hook 3 — Stop: queue up the next pattern for the next session

Goal: when a Claude Code session ends, append the next highest-impact pattern to a queue file. When you start the next session, Claude Code reads the queue first and resumes the loop without you typing anything.

.claude/hooks/stop.yml:

run: |
  mkdir -p .claude/state
  eyepup todo --limit 1 > .claude/state/next-pattern.txt 2>/dev/null || true
  if [ -s .claude/state/next-pattern.txt ]; then
    echo ""
    echo "▶ Next session pickup:"
    cat .claude/state/next-pattern.txt
  fi

Combined with a session-start prompt:

# CLAUDE.md addition
At the start of every session, if .claude/state/next-pattern.txt exists,
read it and ask whether to ship that pattern's recommended fix.

The flow becomes:

  1. You finish session N (shipped one fix)
  2. Stop hook writes the next pattern to .claude/state/next-pattern.txt
  3. You open Claude Code session N+1
  4. Claude reads next-pattern.txt, asks "ship the Mobile Footer CTA fix?"
  5. You say yes (or no, and it pulls the next one)

That's the auto-loop, fully observable in the agent's chat history.

Putting them together

.claude/hooks/:

pre-tool-use.yml    # Hook 1 — surface top patterns before edit
post-tool-use.yml   # Hook 2 — log every change
stop.yml            # Hook 3 — queue next pattern

Wire all three and your Claude Code sessions self-prioritise: every session opens with the highest-impact pattern in scope, the agent commits the change with a log row, and the next session picks up where this one left off.

What goes wrong

Two failure modes worth knowing:

  1. eyepup not authed on a teammate's machine. The || true / || echo failsafes handle this gracefully — the hook prints a one-line warning and keeps going. Without those, the hook crashes Claude Code's lifecycle. Always include the failsafe.
  2. The post-edit hook firing on non-UX edits. If you don't scope paths: carefully, you'll log every package.json bump as a "content_change." Use the paths: filter aggressively. The kind taxonomy is content_change | design_change | pricing_change | feature_ship | bug_fix | other — pick the right one or you're poisoning the dossier agent's reasoning.

What's next

The hooks above work today. The version of this article we'll publish in 2026 H2 swaps the CLI piping for native MCP tool calls, which compresses the round-trip:

# Future shape — when the Eyepup MCP server lands
tools:
  - eyepup__todo
  - eyepup__log
  - eyepup__visitor

Same loop, fewer characters. CLI piping survives indefinitely as the universal fallback for anything that's not MCP-aware (Codex shell tools, GitHub Actions, cron jobs, raw bash).


Set up the loop: npm i -g eyepupeyepup logineyepup install → drop the three hooks above into .claude/hooks/. First closed loop in 10 minutes.