Healing-Factor Documentation

Healing-Factor watches your Laravel application for unhandled exceptions and uses AI to write a fix and open a draft pull request -- automatically. You review the PR and merge it if the fix looks good. No AI-generated code reaches production without your approval.

What it can fix

Any bug that happens while your app is still running and the queue worker can process jobs -- the same kinds of bugs a developer would fix after receiving an alert:

What it cannot fix

The sweet spot: a typo, a missing null check, a wrong query column, a bad method call. The app is running, the error is logged, and the fix is a small code change. That is where Healing-Factor shines.

A note about costs

CLI driver: If you use the CLI driver with a Claude Pro or Max plan, resolutions are covered by your subscription at no additional cost -- Claude Code handles its own authentication and billing.

API driver: Each resolution typically costs a few cents to a few dollars in API usage, depending on the complexity of the exception, the number of conversation turns, and the model you choose. You can control costs by setting max_turns and timeout limits. Check the Anthropic pricing page for current rates.

Installation

1. Install via Composer

composer require arielmejiadev/healing-factor

2. Run the install command

php artisan healing-factor:install

This publishes the config file and migration, runs the migration (with confirmation), and runs driver-specific checks: for the CLI driver it verifies the CLI tool is installed, for the API driver it checks that ANTHROPIC_API_KEY is set.

3. Add environment variables to .env

The required variables depend on your driver. Pick one -- the default is cli, which does not need an API key because Claude Code handles its own authentication.

APP_ENV=staging
HEALING_FACTOR_MONITOR=exception_listener
HEALING_FACTOR_DRIVER=cli

The CLI tool defaults to claude (Claude Code). To use OpenCode instead, add HEALING_FACTOR_CLI_TOOL=opencode.

APP_ENV=staging
HEALING_FACTOR_MONITOR=exception_listener
HEALING_FACTOR_DRIVER=api
ANTHROPIC_API_KEY=sk-ant-...
HEALING_FACTOR_GITHUB_PAT=github_pat_...

Everything else has sensible defaults. See Essential Settings for the full list.

4. Start the queue worker

php artisan queue:work

5. Enable the dashboard

The dashboard is restricted to APP_ENV=local by default. Since you are running in staging, register an auth gate in your AppServiceProvider:

use ArielMejiaDev\HealingFactor\Facades\HealingFactor;

public function boot(): void
{
    HealingFactor::auth(function ($user) {
        return in_array($user->email, [
            'admin@yourcompany.com',
        ]);
    });
}

Then visit /healing-factor to see issues in real time. See Dashboard for more options.

Why APP_ENV=staging? Healing-Factor only processes exceptions in production and staging by default. Errors in local are expected during development. To test locally, set APP_ENV=staging.

Requirements

Depending on which driver you choose (explained in CLI vs API Driver):

Not sure which driver to pick? Read the CLI vs API Driver section first. If you just want to get started quickly, the API driver is the easiest -- it only needs an API key.

Prepare Your Server

Instructions for Ubuntu-based servers (Laravel Forge, Laravel Cloud, DigitalOcean, AWS). Both drivers require the GitHub CLI for pull request creation.

Install and authenticate the GitHub CLI

sudo mkdir -p -m 755 /etc/apt/keyrings
wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update && sudo apt install gh -y

Authenticate using your GitHub personal access token:

echo "github_pat_..." | gh auth login --with-token

Install the AI driver

# Install Node.js LTS (if not already present)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs

# Install Claude Code
npm install -g @anthropic-ai/claude-code

Claude Code authenticates via the ANTHROPIC_API_KEY environment variable -- no interactive login needed. Generate one here, set it in your .env, and Healing-Factor passes it to the CLI automatically. No expiration, no re-authentication.

To use OpenCode instead, follow its installation guide and set HEALING_FACTOR_CLI_TOOL=opencode.

# No additional installation needed.
# Just set these in your .env:
ANTHROPIC_API_KEY=sk-ant-...
HEALING_FACTOR_GITHUB_PAT=github_pat_...

The API driver calls the Anthropic API directly from PHP -- no CLI tools required beyond gh.

Verify: Run gh auth status as the same user that runs your queue worker to confirm authentication is working.

How It Works

  1. An exception occurs in your Laravel app.
  2. Healing-Factor captures it via the built-in exception listener or a webhook from an error monitoring service (Nightwatch, Bugsnag).
  3. The exception is fingerprinted (a unique hash is generated from the exception class, message, and file location so the same error is not processed twice) and debounced (if the same fingerprint was seen recently, it is skipped to avoid duplicate work).
  4. A queued job creates an isolated git worktree (a separate copy of your repository on a new branch -- your production working directory is never touched) and runs the AI inside it.
  5. The AI analyzes the code, writes a fix, commits, pushes, and opens a draft pull request.
  6. The worktree is automatically cleaned up, even if the AI fails.
  7. You review the PR and merge.

Your production code is safe. The AI never modifies files in your production working directory. It works in an isolated git worktree on a separate branch, and the result is always a draft PR that requires your review before merging.

CLI vs API Driver

Healing-Factor supports two drivers for running the AI. Choose one based on your environment.

CLI DriverAPI Driver
Best forServers with Claude Code or OpenCode installedStaging/production servers without CLI tools (DigitalOcean, Laravel Cloud, AWS)
Requiresclaude or opencode CLI binary on the serverOnly ANTHROPIC_API_KEY and HEALING_FACTOR_GITHUB_PAT
How it worksSpawns a CLI subprocessCalls the Anthropic Messages API with tool use
Tool accessFull CLI tool capabilities6 curated tools (read, write, edit, search, list, run command)
Command safetyCLI tool's own safety modelExplicit command allowlist that you control

If you are not sure, use the API driver. It works on any server, requires no extra installation, and gives you fine-grained control over what commands the AI can run. Set HEALING_FACTOR_DRIVER=api in your .env.

Monitor Strategy

A monitor is how Healing-Factor learns about exceptions. It supports one active monitor at a time. There are three options:

Exception Listener (recommended for getting started)

HEALING_FACTOR_MONITOR=exception_listener

No setup needed beyond the env variable. Healing-Factor hooks into Laravel's error handling and captures unhandled exceptions directly. This is the simplest option and works without any external service.

The listener includes a recursion guard to prevent infinite loops if Healing-Factor's own code triggers an exception.

Nightwatch

HEALING_FACTOR_MONITOR=nightwatch

Receives webhooks from Laravel Nightwatch. Configure your Nightwatch project to send webhooks to your Healing-Factor endpoint (POST /healing-factor/webhook). Healing-Factor processes issue.opened and issue.reopened events.

You must also set a webhook secret for signature verification:

HEALING_FACTOR_WEBHOOK_SECRET=a-strong-random-secret

Bugsnag

HEALING_FACTOR_MONITOR=bugsnag

Receives webhooks from Bugsnag. Point a Bugsnag webhook at your Healing-Factor endpoint. It processes exception and firstException triggers.

HEALING_FACTOR_WEBHOOK_SECRET=your-bugsnag-webhook-token

If you are not sure, use the exception listener. It requires no external services, no webhook configuration, and works out of the box. You can switch to Nightwatch or Bugsnag later when you want centralized error management.

Essential Settings

All configuration lives in config/healing-factor.php. Every key can be overridden via environment variables. Most users only need to set a few values -- the rest have sensible defaults.

Master Switch

'enabled' => env('HEALING_FACTOR_ENABLED', true),

Set to false to disable all processing. Webhooks will still return 200 but will not create issues.

Environment Whitelist

'environments' => ['production', 'staging'],

Healing-Factor only processes exceptions in these environments. Set to an empty array [] to allow all environments.

Dry Run

'dry_run' => env('HEALING_FACTOR_DRY_RUN', false),

When enabled, Healing-Factor logs what it would do but never runs the AI. Useful for testing your setup before going live.

Driver

'driver' => env('HEALING_FACTOR_DRIVER', 'cli'),

Choose cli or api. See CLI vs API Driver for guidance.

CLI Tool

'cli_tool' => env('HEALING_FACTOR_CLI_TOOL', 'claude'),

Only applies when using the CLI driver. Choose claude (Claude Code) or opencode (OpenCode).

AI Model

'model' => env('HEALING_FACTOR_MODEL', null),

Override the default model. When null, the CLI tool or API uses its own default.

API Keys

'api_keys' => [
    'anthropic' => env('ANTHROPIC_API_KEY'),
    'github_pat' => env('HEALING_FACTOR_GITHUB_PAT'),
],

Generating a GitHub PAT

Go to GitHub → Settings → Developer settings → Fine-grained tokens and create a token scoped to your repository with these permissions:

PermissionAccess
ContentsRead and write
Pull requestsRead and write
MetadataRead (auto-selected)

Never commit API keys to version control. Always use environment variables.

Ignored Exceptions

'ignored_exceptions' => [
    \Symfony\Component\ErrorHandler\Error\FatalError::class,
    \OutOfMemoryError::class,
    \Illuminate\Http\Exceptions\ThrottleRequestsException::class,
    \Symfony\Component\HttpKernel\Exception\HttpException::class,
    \Illuminate\Session\TokenMismatchException::class,
],

Exceptions that should never be processed -- infrastructure issues, rate limits, and other errors that cannot be fixed by a code change. Add your own exception classes to this array.

Ignored Message Patterns

'ignored_message_patterns' => [
    '/^__VSCODE_LARAVEL_STARTUP_ERROR__:/i',
],

Regex patterns matched against the exception message. If any pattern matches, the error is silently discarded. This is useful for filtering out duplicate errors injected by IDE plugins -- for example, the VSCode Laravel extension prefixes server errors with __VSCODE_LARAVEL_STARTUP_ERROR__:, which produces a duplicate of every real exception.

Add your own patterns for any IDE or tooling prefix that duplicates server-side errors:

'ignored_message_patterns' => [
    '/^__VSCODE_LARAVEL_STARTUP_ERROR__:/i',
    '/^__PHPSTORM_HELPER__:/i',
],

Queue & Process

Queue

'queue' => [
    'connection' => env('HEALING_FACTOR_QUEUE_CONNECTION', null),
    'name' => env('HEALING_FACTOR_QUEUE_NAME', null),
],

null uses your app's default queue. Consider using a dedicated queue to keep long-running resolution jobs from blocking your other work.

Process Settings

'process' => [
    'timeout' => (int) env('HEALING_FACTOR_PROCESS_TIMEOUT', 3600),
    'max_turns' => (int) env('HEALING_FACTOR_MAX_TURNS', 25),
    'working_directory' => null,
],

Debounce

'debounce_minutes' => (int) env('HEALING_FACTOR_DEBOUNCE_MINUTES', 5),

After processing an exception, Healing-Factor ignores the same fingerprint for this many minutes. This prevents a flood of identical exceptions from creating duplicate work. See Deduplication Details for the full deduplication strategy.

Logging

'log_channel' => env('HEALING_FACTOR_LOG_CHANNEL', 'healing-factor'),

If this channel does not exist in config/logging.php, Healing-Factor auto-configures a daily log at storage/logs/healing-factor.log with 14-day retention. You can override this by defining the channel yourself in your logging config.

Pull Request Settings

'pr' => [
    'draft' => true,
    'labels' => ['healing-factor', 'auto-fix'],
    'reviewers' => [],
    'branch_prefix' => 'healing-factor/fix',
],

Dashboard

Healing-Factor includes a read-only web dashboard for browsing issues. Visit /healing-factor (or your custom path) to access it.

Configuration

'dashboard' => [
    'enabled' => env('HEALING_FACTOR_DASHBOARD_ENABLED', true),
    'path' => env('HEALING_FACTOR_DASHBOARD_PATH', 'healing-factor'),
    'middleware' => ['web', 'auth'],
],

Authorization

By default, the dashboard is only accessible when APP_ENV=local. Since Healing-Factor typically runs in staging or production, you will need to register an authorization gate in your AppServiceProvider to access the dashboard in those environments:

use ArielMejiaDev\HealingFactor\Facades\HealingFactor;

public function boot(): void
{
    HealingFactor::auth(function ($user) {
        return in_array($user->email, [
            'admin@yourcompany.com',
        ]);
    });
}

Important: If you skip this step, the dashboard will return a 403 in staging and production. This is intentional -- it prevents unauthorized access by default.

Features

The dashboard theme uses CSS custom properties compatible with Laravel's starter kits (shadcn/ui). You can customize it by publishing the views (php artisan vendor:publish --tag=healing-factor-views) and editing the CSS variables or replacing the stylesheet with your own. See Theme Customization in the Advanced section for details.

Artisan Commands

Essential Commands

These are the commands you will use regularly.

healing-factor:install

Publishes config and migration, runs migration, and runs driver-specific checks (CLI tool availability for the CLI driver, ANTHROPIC_API_KEY for the API driver). Run this once during installation.

php artisan healing-factor:install

healing-factor:status

Displays a table of issues with summary statistics. Your go-to command for checking what Healing-Factor is doing.

php artisan healing-factor:status
php artisan healing-factor:status --limit=50

healing-factor:test

Creates a test issue and dispatches it for resolution. Use this to verify your setup works end-to-end.

php artisan healing-factor:test
php artisan healing-factor:test --exception=RuntimeException
php artisan healing-factor:test --sync

healing-factor:retry {id}

Retries a failed issue by resetting it to pending and dispatching it again.

php artisan healing-factor:retry 42

Maintenance Commands

These commands keep your issues table healthy. Schedule them and forget about them.

healing-factor:recover-stale

Finds issues stuck in resolving status beyond the configured process timeout and marks them as failed. This can happen if a queue worker crashes mid-resolution. Once marked as failed, you can retry them from the dashboard or via healing-factor:retry.

php artisan healing-factor:recover-stale
php artisan healing-factor:recover-stale --minutes=90

healing-factor:prune

Deletes resolved and failed issues older than the retention period (default: 30 days). Keeps your database table from growing indefinitely.

php artisan healing-factor:prune
php artisan healing-factor:prune --days=7

What is the difference between recover-stale and prune? recover-stale rescues stuck issues so they can be retried. prune deletes old finished issues to save database space. They serve different purposes and should both be scheduled.

Recommended Schedule

Add both maintenance commands to your routes/console.php:

use Illuminate\Support\Facades\Schedule;

// Recover stuck issues every hour
Schedule::command('healing-factor:recover-stale')->hourly();

// Clean up old issues daily
Schedule::command('healing-factor:prune')->daily();

Events

Healing-Factor fires events at each stage of the resolution lifecycle. Use these to send notifications, update dashboards, or trigger other workflows.

EventFired WhenProperties
IssueCreatedIssue is created from webhook or exceptionIssue $issue
IssueResolvingResolution starts (status changes to resolving)Issue $issue
IssueResolvedResolution succeeds (status changes to resolved)Issue $issue
IssueResolutionFailedResolution fails (status changes to failed)Issue $issue, string $reason

Example: Send a Slack notification when a fix is ready

use ArielMejiaDev\HealingFactor\Events\IssueResolved;

Event::listen(IssueResolved::class, function (IssueResolved $event) {
    Notification::route('slack', config('services.slack.webhook'))
        ->notify(new IssueFixedNotification($event->issue));
});

Issue Model

The Issue model (ArielMejiaDev\HealingFactor\Models\Issue) is stored in the healing_factor_issues table. Here are the fields you will interact with most often:

Full Field Reference

FieldDescription
fingerprintSHA-256 hash uniquely identifying the exception
sourceOrigin: nightwatch, bugsnag, exception_listener, or test
titleHuman-readable issue title
exception_classPHP exception class name
exception_messageException message
stacktraceStack trace text
statuspending, resolving, resolved, or failed
categoryMatched category name (e.g., quick_fixes)
cli_toolCLI tool used (claude or opencode)
branch_nameGenerated Git branch name
pr_urlURL of the created pull request
failure_reasonReason for failure (if failed)
attemptsNumber of resolution attempts
payloadFull webhook payload (JSON)

Query Scopes

use ArielMejiaDev\HealingFactor\Models\Issue;

Issue::pending()->get();
Issue::resolving()->get();
Issue::resolved()->get();
Issue::failed()->get();
Issue::stale(30)->get(); // Resolved/failed older than 30 days

Facade

use ArielMejiaDev\HealingFactor\Facades\HealingFactor;

HealingFactor::isEnabled();  // bool
HealingFactor::isDryRun();   // bool
HealingFactor::manager();    // HealingFactorManager instance

Exception Categories

Categories let you customize how the AI handles different exception types. For example, you might want simple errors to use a shorter timeout and fewer turns, while complex errors get more room to work.

'categories' => [
    'quick_fixes' => [
        'cli_tool' => 'claude',
        'model' => null,
        'timeout' => 1800,     // 30 minutes
        'max_turns' => 15,
        'prompt' => null,       // null = use default prompt
        'exceptions' => [
            \ErrorException::class,
            \TypeError::class,
        ],
    ],
    'complex_fixes' => [
        'cli_tool' => 'claude',
        'model' => null,
        'timeout' => 3600,     // 1 hour
        'max_turns' => 30,
        'prompt' => null,
        'exceptions' => [
            \LogicException::class,
            \RuntimeException::class,
        ],
    ],
],

Each category can override: cli_tool, model, timeout, max_turns, and prompt. Exceptions not matching any category use the top-level defaults.

Most users do not need categories. The defaults work well for the majority of exceptions. Only configure categories if you want to fine-tune behavior for specific exception types or control costs more precisely.

Custom Prompts

You can customize the prompt sent to the AI, either globally or per category.

// Per category
'categories' => [
    'quick_fixes' => [
        'prompt' => 'Fix this {exception_class} in the Laravel app: {exception_message}. Branch: {branch_name}',
    ],
],

// Global fallback
'prompt' => 'You are debugging a Laravel app. Fix: {title}',

Available Placeholders

PlaceholderDescription
{title}Issue title
{exception_class}PHP exception class name
{exception_message}Exception message
{stacktrace}Stack trace
{branch_name}Generated branch name
{organization_id}Nightwatch organization ID
{application_id}Nightwatch application ID
{environment_id}Nightwatch environment ID
{source}Issue source (nightwatch, bugsnag, exception_listener)

Webhook Payload Formats

This section is only relevant if you use the Nightwatch or Bugsnag monitor. If you use the exception listener, you can skip this entirely.

Webhook Endpoint

POST /healing-factor/webhook

The path is configurable:

'webhook' => [
    'path' => env('HEALING_FACTOR_WEBHOOK_PATH', 'healing-factor/webhook'),
    'secret' => env('HEALING_FACTOR_WEBHOOK_SECRET'),
    'middleware' => [],
],

CSRF verification is automatically excluded from the webhook route. You can add custom middleware via the middleware array.

Signature Verification

Set HEALING_FACTOR_WEBHOOK_SECRET to a strong random string and share this secret with your monitor service.

If HEALING_FACTOR_WEBHOOK_SECRET is not set, signature verification is skipped and a warning is logged. Always set this in production.

Nightwatch Payload

{
    "event": "issue.opened",
    "payload": {
        "organization_id": "org-123",
        "application_id": "app-456",
        "environment": { "id": "env-789" },
        "issue": {
            "title": "ErrorException: Undefined variable $foo",
            "exception_class": "ErrorException",
            "message": "Undefined variable $foo",
            "stacktrace": "#0 /app/Http/Controllers/..."
        }
    }
}

Bugsnag Payload

{
    "trigger": "exception",
    "error": {
        "exceptionClass": "RuntimeException",
        "message": "Something went wrong",
        "stackTrace": "..."
    }
}

API Driver Deep Dive

The API driver calls the Anthropic Messages API directly, giving Claude a curated set of tools to read, edit, search files, and run git commands -- all orchestrated from PHP. No CLI tool installation required.

Available Tools

ToolDescription
read_fileRead a file's contents
write_fileWrite content to a file (creates parent directories)
edit_fileReplace an exact string match in a file
list_directoryList files and subdirectories
search_filesSearch for patterns with grep
run_commandRun an allowlisted shell command

Security Model

Optional Configuration

HEALING_FACTOR_API_MODEL=claude-sonnet-4-6
HEALING_FACTOR_API_MAX_TOKENS=8192
HEALING_FACTOR_API_MAX_TURNS=25

Customizing Allowed Commands

'api' => [
    'allowed_commands' => [
        'git',
        'php artisan test',
        './vendor/bin/pest',
        './vendor/bin/phpunit',
        'composer dump-autoload',
        'gh pr create',
    ],
],

A command is allowed if it exactly matches an entry, or starts with an entry followed by a space. For example, git allows git status, git commit -m "fix", and so on. Add your own commands as needed.

Security Considerations

Healing-Factor is designed with multiple layers of protection. Here is how each concern is addressed.

The AI never touches production directly

Every fix happens inside an isolated git worktree on a separate branch. Your production working directory is never modified. The result is always a draft pull request that requires a human to review and merge.

Webhook security

Webhook payloads are verified using HMAC-SHA256 (Nightwatch) or Bearer tokens (Bugsnag). Always set HEALING_FACTOR_WEBHOOK_SECRET in production to prevent unauthorized webhook submissions.

No shell injection

CLI commands are built as arrays and passed to Laravel's Process facade, which prevents shell injection attacks. The API driver additionally enforces an explicit command allowlist.

API keys are environment-only

API keys are never stored in config files. They are always loaded from environment variables.

About --dangerously-skip-permissions

When using the CLI driver, Claude Code runs with the --dangerously-skip-permissions flag. This sounds alarming, but here is the context: Claude Code normally asks for interactive permission before reading or editing files. Since Healing-Factor runs unattended (in a queue worker), it cannot prompt a human for permission on each file operation. The flag enables unattended operation.

This is safe in practice because:

Recursion guard

The exception listener includes a guard that prevents infinite loops if Healing-Factor's own operations trigger exceptions.

Deduplication Details

Healing-Factor uses three layers to prevent processing the same exception multiple times:

  1. Cache debounce -- When an exception is fingerprinted, the fingerprint is stored in cache. If the same fingerprint appears within the debounce window (default: 5 minutes), it is silently skipped.
  2. Database check -- If an issue with the same fingerprint already exists in pending or resolving status, the new one is skipped.
  3. Unique constraint -- A (fingerprint, status) database constraint prevents duplicates at the DB level as a final safety net.
'debounce_minutes' => (int) env('HEALING_FACTOR_DEBOUNCE_MINUTES', 5),

You can adjust the debounce window based on your error volume. A shorter window means Healing-Factor will retry the same error sooner if the first fix fails. A longer window reduces duplicate work but may delay retries.

Theme Customization

The dashboard uses CSS custom properties compatible with Laravel's starter kits (React, Vue, Livewire with shadcn/ui).

Option 1: Use your app's existing theme

php artisan vendor:publish --tag=healing-factor-views

Edit resources/views/vendor/healing-factor/dashboard/layout.blade.php: replace the <style> block with your app's compiled CSS:

{{-- Remove the <style>...</style> block --}}

{{-- Add your app's compiled stylesheet --}}
@vite(['resources/css/app.css'])

Option 2: Customize the built-in theme

Publish the views and edit the CSS variables directly. Each variable takes three OKLCH values: lightness chroma hue.

VariableUsage
--backgroundPage body background
--foregroundDefault text color
--card / --card-foregroundCard sections
--primary / --primary-foregroundPrimary buttons, active tabs
--muted / --muted-foregroundTable headers, code blocks
--borderTable/card borders

Uninstalling

To remove Healing-Factor from your project:

  1. Remove the scheduled commands from routes/console.php if you added them.
  2. Remove the auth gate from AppServiceProvider if you added one.
  3. Drop the table: php artisan tinker --execute="Schema::dropIfExists('healing_factor_issues')"
  4. Remove the config file: rm config/healing-factor.php
  5. Remove published views if any: rm -rf resources/views/vendor/healing-factor
  6. Uninstall the package: composer remove arielmejiadev/healing-factor
  7. Remove the HEALING_FACTOR_* environment variables from your .env.

Troubleshooting

I installed it but nothing happens

This is the most common issue. Work through this checklist:

  1. Check APP_ENV -- Healing-Factor only runs in production and staging by default. If you are in local, set APP_ENV=staging for testing.
  2. Check HEALING_FACTOR_ENABLED -- Make sure it is not set to false.
  3. Check HEALING_FACTOR_MONITOR -- If you are using nightwatch or bugsnag, exceptions are only captured via webhook. Try exception_listener for the simplest setup.
  4. Check your queue worker -- Is it running? Healing-Factor dispatches jobs to the queue. Run php artisan queue:work --timeout=3700 and trigger an error.
  5. Check the logs -- Look at storage/logs/healing-factor.log for clues. If the file does not exist, the package may not be receiving exceptions.
  6. Run the test command -- php artisan healing-factor:test --sync bypasses the queue and runs immediately. If this works but real exceptions do not, the issue is likely with your monitor or queue.

CLI tool not found

Make sure claude or opencode is in the PATH of the user running the queue worker. Run which claude to verify. If you cannot install CLI tools on your server, switch to the API driver.

Jobs timing out

Increase HEALING_FACTOR_PROCESS_TIMEOUT and ensure your queue worker's --timeout flag is higher than the process timeout. For example, if the process timeout is 3600, set --timeout=3700.

If you use queue:listen (common in development for fresh code on each job), you must also pass the timeout flag -- its default is only 60 seconds:

php artisan queue:listen --timeout=3700

Duplicate PRs

Check your debounce window (HEALING_FACTOR_DEBOUNCE_MINUTES) and run php artisan healing-factor:status to see active issues. See Deduplication Details for more information.

Webhook returning 403

Your webhook secret does not match. Verify that HEALING_FACTOR_WEBHOOK_SECRET in your .env matches the secret configured in your monitor service (Nightwatch or Bugsnag).

Dashboard returning 403

The dashboard is restricted to APP_ENV=local by default. To access it in staging or production, register an auth gate. See Dashboard Authorization.

Issues stuck in "resolving" status

Run php artisan healing-factor:recover-stale to mark them as failed, then retry from the dashboard or via php artisan healing-factor:retry {id}. Schedule the command hourly to prevent build-up.

Dry run mode

Enable dry run to test your setup without calling the AI:

HEALING_FACTOR_DRY_RUN=true

Check storage/logs/healing-factor.log for output.

Infinite loop with exception listener

The recursion guard should prevent this. If you see repeated exceptions, check that your database is reachable and the queue worker is not itself failing. Run php artisan healing-factor:status to inspect the state.