Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/sync-hook-events.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Sync Hook Event Types

on:
schedule:
- cron: '7 8 * * *' # daily at 08:07 UTC
workflow_dispatch:

jobs:
sync-hooks:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- uses: actions/checkout@v4

- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code

# Claude only gets its own API credentials — no GITHUB_TOKEN
- name: Analyze hook coverage and edit files
env:
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
FAILPROOFAI_TELEMETRY_DISABLED: "1"
CLAUDE_SKIP_SETUP_MODAL: "true"
run: |
claude \
--model claude-sonnet-4-6 \
--allowedTools "Read,Edit,Glob,Grep,WebFetch" \
--dangerously-skip-permissions \
-p "$(cat scripts/sync-hook-events-prompt.md)"

# PR creation is handled here — GITHUB_TOKEN never touches Claude
- name: Create PR if changes detected
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RESULT=$(cat .sync-hook-events-output.json 2>/dev/null || echo '{"changed":false}')
CHANGED=$(echo "$RESULT" | jq -r '.changed')

if [ "$CHANGED" != "true" ]; then
echo "Hook coverage is up to date. No PR needed."
exit 0
fi

# Check for an existing open sync PR to avoid duplicates
EXISTING=$(gh pr list --base main --search "[auto] sync hook event types" --state open --json number --jq length)
if [ "$EXISTING" -gt 0 ]; then
echo "Sync PR already open. Skipping."
exit 0
fi

BRANCH="auto/sync-hook-events-$(date +%Y%m%d-%H%M)"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add src/hooks/types.ts __tests__/hooks/manager.test.ts
git commit -m "feat: sync hook event types with Claude Code docs"
git push origin "$BRANCH"

PR_TITLE=$(echo "$RESULT" | jq -r '.prTitle')
PR_BODY=$(echo "$RESULT" | jq -r '.prBody')
gh pr create \
--title "$PR_TITLE" \
--body "$PR_BODY" \
--base main \
--head "$BRANCH"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ next-env.d.ts
# custom hooks loader temp files
*.__failproofai_tmp__.*

# sync-hook-events workflow output (ephemeral, generated in CI)
.sync-hook-events-output.json

# package manager lockfiles (bun.lock is tracked; bun.lockb is binary)
package-lock.json

Expand Down
6 changes: 3 additions & 3 deletions __tests__/hooks/manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe("hooks/manager", () => {
});

describe("installHooks", () => {
it("installs hooks for all 17 event types into empty settings", async () => {
it("installs hooks for all 26 event types into empty settings", async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(readFileSync).mockReturnValue("{}");

Expand All @@ -69,7 +69,7 @@ describe("hooks/manager", () => {
expect(path).toBe(USER_SETTINGS_PATH);

const written = JSON.parse(content as string);
expect(Object.keys(written.hooks)).toHaveLength(17);
expect(Object.keys(written.hooks)).toHaveLength(26);

for (const [eventType, matchers] of Object.entries(written.hooks)) {
expect(matchers).toHaveLength(1);
Expand Down Expand Up @@ -217,7 +217,7 @@ describe("hooks/manager", () => {
expect(writeFileSync).toHaveBeenCalledOnce();
const [, content] = vi.mocked(writeFileSync).mock.calls[0];
const written = JSON.parse(content as string);
expect(Object.keys(written.hooks)).toHaveLength(17);
expect(Object.keys(written.hooks)).toHaveLength(26);
});

it("uses 'where' on Windows and handles multi-line output", async () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "failproofai",
"version": "0.0.1-beta.11",
"version": "0.0.1-beta.12",
"description": "Open-source hooks, policies, and project visualization for Claude Code & Agents SDK",
"bin": {
"failproofai": "./bin/failproofai.mjs"
Expand Down
60 changes: 60 additions & 0 deletions scripts/sync-hook-events-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
You are an automated agent running in GitHub Actions to keep failproofai's hook
event types in sync with the official Claude Code documentation.

## Your task

1. Fetch the Claude Code hooks reference pages using WebFetch:
- https://code.claude.com/docs/en/hooks (full reference — has the complete event table)
- https://code.claude.com/docs/en/hooks-guide (guide — also has a summary event table)
Extract the complete list of all hook event type names (e.g. SessionStart,
PreToolUse, PostToolUse, etc.) from the event lifecycle/trigger table.
Use both pages; union the results if they differ. Prefer the reference page.

2. Read `src/hooks/types.ts` and extract the current `HOOK_EVENT_TYPES` array
(the TypeScript `as const` array of strings).

3. Diff the two lists:
- **added**: event types in the docs but NOT in our array
- **removed**: event types in our array but NOT in the docs

4. If there are NO differences:
Write the following JSON to `.sync-hook-events-output.json` in the repo root:
```json
{ "changed": false }
```
Then stop.

5. If there are differences:

a. Update `HOOK_EVENT_TYPES` in `src/hooks/types.ts`:
- Add new event types (append after the last existing entry, before `] as const`)
- Remove stale event types if any

b. Update `__tests__/hooks/manager.test.ts` — find the hardcoded event-type
counts and update them to the new total:
- The test description string matching `all N event types`
- The `toHaveLength(N)` assertion(s) that check `Object.keys(written.hooks)`
Search by the current count number to locate them.

c. Write the following JSON to `.sync-hook-events-output.json` in the repo root:
```json
{
"changed": true,
"added": ["EventA", "EventB"],
"removed": ["EventC"],
"prTitle": "[auto] sync hook event types with Claude Code docs",
"prBody": "..."
}
```
The `prBody` must be a Markdown string containing:
- List of **added** event types (or "none")
- List of **removed** event types (or "none")
- Source URLs used
- A note: "CI must pass and this PR must be reviewed before merging."

## Constraints

- **Only edit `src/hooks/types.ts`, `__tests__/hooks/manager.test.ts`, and
`.sync-hook-events-output.json`**. No other files.
- Do NOT run any shell commands (no git, no gh, no bun).
- Do NOT modify `policy-evaluator.ts`, `manager.ts`, or any other source file.
13 changes: 11 additions & 2 deletions src/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,28 @@ export const HOOK_EVENT_TYPES = [
"SessionEnd",
"UserPromptSubmit",
"PreToolUse",
"PermissionRequest",
"PermissionDenied",
"PostToolUse",
"PostToolUseFailure",
"PermissionRequest",
"Notification",
"SubagentStart",
"SubagentStop",
"TaskCreated",
"TaskCompleted",
"Stop",
"StopFailure",
"TeammateIdle",
"TaskCompleted",
"InstructionsLoaded",
"ConfigChange",
"CwdChanged",
"FileChanged",
"WorktreeCreate",
"WorktreeRemove",
"PreCompact",
"PostCompact",
"Elicitation",
"ElicitationResult",
] as const;

export type HookEventType = (typeof HOOK_EVENT_TYPES)[number];
Expand Down