# How CI Validation Works with `plugin-load-check.yml` in the i-have-adhd Repository

> Learn how CI validation works with plugin-load-check.yml in the i-have-adhd repo. Discover how it splits jobs for unit-testing native hooks and end-to-end plugin loading to catch errors before merges.

- Repository: [Ayoub Ghriss/i-have-adhd](https://github.com/ayghri/i-have-adhd)
- Tags: how-to-guide
- Published: 2026-08-22

---

**TLDR: The repository uses a dedicated GitHub Actions workflow at [`.github/workflows/plugin-load-check.yml`](https://github.com/ayghri/i-have-adhd/blob/main/.github/workflows/plugin-load-check.yml) that splits validation into two jobs — `hook-parity` for unit-testing native hook implementations and `load` for end-to-end plugin loading via the Claude Code CLI — to catch both static schema and runtime errors before any pull request merges.**

The `ayghri/i-have-adhd` repository contains a Claude Code plugin that ships always-on hooks and skill definitions. Maintaining such a plugin requires more than schema validation — hooks can fail to load at runtime even when their JSON declarations are syntactically valid. That's why the maintainers built a two-pronged CI validation pipeline using [`plugin-load-check.yml`](https://github.com/ayghri/i-have-adhd/blob/main/plugin-load-check.yml), a GitHub Actions workflow that verifies both hook parity across operating systems and actual plugin installation through the Claude Code CLI. This article breaks down exactly how that workflow is configured, what each job validates, and how you can reproduce the checks locally.

## What Triggers the CI Validation Workflow

The [`plugin-load-check.yml`](https://github.com/ayghri/i-have-adhd/blob/main/plugin-load-check.yml) workflow is defined in [`.github/workflows/plugin-load-check.yml`](https://github.com/ayghri/i-have-adhd/blob/main/.github/workflows/plugin-load-check.yml) and runs on two event types, as specified at lines L6-L15 of the workflow file:

- **`pull_request`** — fires only when specific paths change: `.claude-plugin/**`, `hooks/**`, `skills/**`, the test file [`tests/test_always_on_hooks.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_always_on_hooks.py), or the workflow file itself. This path filtering keeps CI fast by avoiding unnecessary runs on unrelated changes.
- **`push`** — runs for every commit delivered to the `main` branch, guaranteeing that the deployed plugin is always validated.

## Inside the Workflow: Two CI Jobs, Two Validation Layers

The workflow defines two jobs that complement each other. According to the source at `plugin-load-check.yml#L18-L30` and `#L31-L48`, these are the `hook-parity` job and the `load` job.

### The `hook-parity` Job: Cross-Platform Unit Testing

The `hook-parity` job runs on a matrix of both Ubuntu and Windows runners. Its purpose is to verify that the native hook implementations load correctly across platforms — catching platform-specific bugs before they reach users.

The job executes the following steps:

1. Check out the repository with `actions/checkout@v4`.
2. Set up Python 3.x using `actions/setup-python@v5`.
3. Run the unit test suite with this command:

```bash
python -m unittest tests.test_always_on_hooks -v

```

This command exercises the hook implementations against the test suite defined in [`tests/test_always_on_hooks.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_always_on_hooks.py). If any hook fails to load or behave as expected, the job fails, blocking the pull request from merging.

### The `load` Job: End-to-End Plugin Installation

The `load` job runs on Ubuntu only and simulates a real user environment by installing the plugin through the official Claude Code CLI. It performs these steps:

1. Check out the repository.
2. Install the Claude Code CLI globally:

```bash
npm install -g @anthropic-ai/claude-code

```

3. Create a temporary, sandboxed config directory:

```bash
export CLAUDE_CONFIG_DIR="$(mktemp -d)"
mkdir -p "$CLAUDE_CONFIG_DIR"

```

4. Add the current checkout to the Claude marketplace:

```bash
claude plugin marketplace add "$GITHUB_WORKSPACE"

```

5. Install the plugin itself:

```bash
claude plugin install i-have-adhd@i-have-adhd

```

6. List installed plugins and grep for the enabled marker:

```bash
claude plugin list | grep "✔ enabled"

```

If the `✔ enabled` marker is absent after installation, the step fails with an explicit error message, causing the entire workflow to fail. This step catches dynamic loading issues that static schema validation would miss — for example, duplicate hook declarations that Claude Code's loader rejects at runtime.

## Why You Need Both Static and Dynamic Validation

Schecking schema validation alone cannot catch runtime load errors. A [`.claude-plugin/plugin.json`](https://github.com/ayghri/i-have-adhd/blob/main/.claude-plugin/plugin.json) or [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json) can be perfectly valid JSON yet still fail when the CLI attempts to instantiate the hooks. The `hook-parity` job covers static and behavioral correctness through the Python unit tests, while the `load` job covers dynamic loading end-to-end. Together they provide a comprehensive safety net that no single check can deliver.

The key files involved in this validation pipeline:

| File | Role in CI Validation |
|------|----------------------|
| [`.github/workflows/plugin-load-check.yml`](https://github.com/ayghri/i-have-adhd/blob/main/.github/workflows/plugin-load-check.yml) | The workflow that orchestrates both jobs |
| [`tests/test_always_on_hooks.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_always_on_hooks.py) | Unit test suite used by the `hook-parity` job |
| [`.claude-plugin/plugin.json`](https://github.com/ayghri/i-have-adhd/blob/main/.claude-plugin/plugin.json) | Plugin manifest parsed by the Claude Code CLI |
| [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json) | Central declaration of always-on hook definitions |
| [`skills/i-have-adhd/agents/openai.yaml`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/agents/openai.yaml) | Example skill definition that affects plugin behavior |

## Running the CI Validation Locally

You can replicate both CI checks on your own machine. For the hook parity test:

```bash
python -m unittest tests.test_always_on_hooks -v

```

For the end-to-end plugin load check:

```bash

# Install Claude Code CLI (once)

npm install -g @anthropic-ai/claude-code

# Create a temporary config directory

export CLAUDE_CONFIG_DIR="$(mktemp -d)"
mkdir -p "$CLAUDE_CONFIG_DIR"

# Add the current repository to the marketplace and install

claude plugin marketplace add .
claude plugin install i-have-adhd@i-have-adhd

# Confirm the plugin is enabled

claude plugin list | grep "✔ enabled"

```

## Summary

- **[`plugin-load-check.yml`](https://github.com/ayghri/i-have-adhd/blob/main/plugin-load-check.yml)** is the single CI workflow that validates every plugin change in the `ayghri/i-have-adhd` repository.
- The **`hook-parity`** job unit-tests native hooks on both Ubuntu and Windows via `python -m unittest tests.test_always_on_hooks -v`.
- The **`load`** job performs a real YAML-free end-to-end install using the Claude Code CLI, verifying that the plugin reports itself as `✔ enabled`.
- Combined, these jobs catch both schema-level and runtime load errors — duplicate hook declarations, invalid manifests, or platform-specific hook failures — before they ever reach a merged commit.

## Frequently Asked Questions

### What does the `hook-parity` job actually test?

The `hook-parity` job runs the Python unit tests in [`tests/test_always_on_hooks.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_always_on_hooks.py) against the native hook implementations on both Ubuntu and Windows. It verifies that the hooks load and behave identically across operating systems, which protects against platform-specific runtime failures.

### Why does the `load` job use a temporary `CLAUDE_CONFIG_DIR`?

The `load` job sets `CLAUDE_CONFIG_DIR` to a temporary directory created with `mktemp -d`. This ensures the plugin installation happens in a sandboxed, isolated configuration space so the CI test doesn't interfere with a developer's real Claude Code configuration or the runner's settings.

### What happens if the plugin does not display the enabled marker?

If the `claude plugin list | grep "✔ enabled"` step does not find the marker, the step exits with a non-zero code and an explicit error message. This fails the `load` job, which blocks the pull request from merging and signals the developer to investigate the plugin's loading issue.

### Why is the workflow triggered only for specific file paths on pull requests?

The `pull_request` trigger filters by paths like `.claude-plugin/**`, `hooks/**`, and `skills/**`. That path whitelist means CI only runs when changes actually affect the plugin's loading behavior, saving runner time and avoiding noisy workflow executions for unrelated code changes.