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:
- Undefined variables and properties --
ErrorException: Undefined variable $foo - Type errors --
TypeError: Argument #1 must be of type string, null given - Bad method calls --
BadMethodCallException: Method does not exist - Query errors --
QueryException: Column not found - Authorization and routing bugs -- misconfigured gates, wrong HTTP methods, missing parameters
- Logic errors --
RuntimeException, off-by-one mistakes, incorrect validation rules
What it cannot fix
- PHP syntax errors -- A
ParseErrorprevents Laravel from booting entirely. - Fatal errors that kill the process --
OutOfMemoryError, segmentation faults. - Infrastructure failures -- database down, Redis unreachable, disk full.
- Environment / config issues -- missing
.envvalues, wrong file permissions. - Issues requiring human judgment -- business logic decisions, UX choices, performance tuning.
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 inproductionandstagingby default. Errors inlocalare expected during development. To test locally, setAPP_ENV=staging.
Requirements
- PHP 8.4+
- Laravel 11, 12, or 13
- GitHub CLI (
gh) installed and authenticated (for PR creation) - A queue worker running (recommended for production)
Depending on which driver you choose (explained in CLI vs API Driver):
- CLI driver: Claude Code or OpenCode CLI installed on the server
- API driver: Only an
ANTHROPIC_API_KEY-- no CLI installation needed
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 statusas the same user that runs your queue worker to confirm authentication is working.
How It Works
- An exception occurs in your Laravel app.
- Healing-Factor captures it via the built-in exception listener or a webhook from an error monitoring service (Nightwatch, Bugsnag).
- 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).
- 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.
- The AI analyzes the code, writes a fix, commits, pushes, and opens a draft pull request.
- The worktree is automatically cleaned up, even if the AI fails.
- 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 Driver | API Driver | |
|---|---|---|
| Best for | Servers with Claude Code or OpenCode installed | Staging/production servers without CLI tools (DigitalOcean, Laravel Cloud, AWS) |
| Requires | claude or opencode CLI binary on the server | Only ANTHROPIC_API_KEY and HEALING_FACTOR_GITHUB_PAT |
| How it works | Spawns a CLI subprocess | Calls the Anthropic Messages API with tool use |
| Tool access | Full CLI tool capabilities | 6 curated tools (read, write, edit, search, list, run command) |
| Command safety | CLI tool's own safety model | Explicit 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=apiin 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'),
],
anthropic-- Your Anthropic API key. Required for the API driver. Also passed to the CLI tool as an environment variable. Generate one at console.anthropic.com.github_pat-- A GitHub personal access token so Healing-Factor can push branches and create PRs in environments without interactivegh auth login.
Generating a GitHub PAT
Go to GitHub → Settings → Developer settings → Fine-grained tokens and create a token scoped to your repository with these permissions:
| Permission | Access |
|---|---|
| Contents | Read and write |
| Pull requests | Read and write |
| Metadata | Read (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,
],
timeout-- Maximum seconds the AI process can run (default: 1 hour). This also controls API costs: a lower timeout means less time for the AI to work.max_turns-- Maximum AI conversation turns. Each turn is one request-response cycle. Lower values mean faster (and cheaper) resolutions, but the AI has less room to explore.working_directory-- Base git repository path.nulldefaults tobase_path().
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',
],
draft-- Create PRs as drafts (recommended). This ensures a human reviews every fix before it reaches production.labels-- Labels applied to every PR. Useful for filtering in GitHub.reviewers-- GitHub usernames to automatically request review from.branch_prefix-- Branch names follow the pattern{prefix}-{slug}-{random}.
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
- Search and filter -- filter issues by title, exception class, or status (Pending, Resolving, Resolved, Failed).
- Issue detail -- view full exception details, stacktrace, CLI output, and PR links.
- Retry failed issues -- pick a model and max turns, then re-queue with one click.
- Auto-refresh -- polling toggle reloads the page every 60 seconds.
- Dark/light theme -- toggle persisted in
localStorage.
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-staleandprune?recover-stalerescues stuck issues so they can be retried.prunedeletes 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.
| Event | Fired When | Properties |
|---|---|---|
IssueCreated | Issue is created from webhook or exception | Issue $issue |
IssueResolving | Resolution starts (status changes to resolving) | Issue $issue |
IssueResolved | Resolution succeeds (status changes to resolved) | Issue $issue |
IssueResolutionFailed | Resolution 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:
status--pending,resolving,resolved, orfailedtitle-- human-readable issue titleexception_class/exception_message-- the PHP exception detailspr_url-- URL of the created pull request (once resolved)failure_reason-- why the resolution failed (if it did)attempts-- number of resolution attempts
Full Field Reference
| Field | Description |
|---|---|
fingerprint | SHA-256 hash uniquely identifying the exception |
source | Origin: nightwatch, bugsnag, exception_listener, or test |
title | Human-readable issue title |
exception_class | PHP exception class name |
exception_message | Exception message |
stacktrace | Stack trace text |
status | pending, resolving, resolved, or failed |
category | Matched category name (e.g., quick_fixes) |
cli_tool | CLI tool used (claude or opencode) |
branch_name | Generated Git branch name |
pr_url | URL of the created pull request |
failure_reason | Reason for failure (if failed) |
attempts | Number of resolution attempts |
payload | Full 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
| Placeholder | Description |
|---|---|
{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.
- Nightwatch: Uses HMAC-SHA256 (a cryptographic hash that proves the webhook came from Nightwatch, not an attacker). The hash is sent in the
X-Nightwatch-SignatureorX-Webhook-Signatureheader. - Bugsnag: Uses a Bearer token in the
Authorizationheader, compared against your secret.
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
| Tool | Description |
|---|---|
read_file | Read a file's contents |
write_file | Write content to a file (creates parent directories) |
edit_file | Replace an exact string match in a file |
list_directory | List files and subdirectories |
search_files | Search for patterns with grep |
run_command | Run an allowlisted shell command |
Security Model
- Path traversal protection -- Every file operation validates that the resolved path stays within the git worktree. The AI cannot read or write files outside the repository.
- Command allowlist -- The
run_commandtool only allows commands you have explicitly configured. - Worktree isolation -- Same as the CLI driver: the AI works in an isolated git worktree, never your production directory.
- Bounded turns -- The agentic loop (the back-and-forth between your server and the API where Claude uses tools, observes results, and decides next steps) is bounded by
max_turnsto prevent runaway usage.
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:
- The CLI runs inside an isolated git worktree, not your production directory.
- All changes go to a draft pull request -- nothing is deployed without your review.
- The process has a timeout limit that prevents it from running indefinitely.
- If you prefer stricter control, use the API driver instead, which enforces an explicit command allowlist and path validation without needing this flag.
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:
- 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.
- Database check -- If an issue with the same fingerprint already exists in
pendingorresolvingstatus, the new one is skipped. - 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.
| Variable | Usage |
|---|---|
--background | Page body background |
--foreground | Default text color |
--card / --card-foreground | Card sections |
--primary / --primary-foreground | Primary buttons, active tabs |
--muted / --muted-foreground | Table headers, code blocks |
--border | Table/card borders |
Uninstalling
To remove Healing-Factor from your project:
- Remove the scheduled commands from
routes/console.phpif you added them. - Remove the auth gate from
AppServiceProviderif you added one. - Drop the table:
php artisan tinker --execute="Schema::dropIfExists('healing_factor_issues')" - Remove the config file:
rm config/healing-factor.php - Remove published views if any:
rm -rf resources/views/vendor/healing-factor - Uninstall the package:
composer remove arielmejiadev/healing-factor - 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:
- Check
APP_ENV-- Healing-Factor only runs inproductionandstagingby default. If you are inlocal, setAPP_ENV=stagingfor testing. - Check
HEALING_FACTOR_ENABLED-- Make sure it is not set tofalse. - Check
HEALING_FACTOR_MONITOR-- If you are usingnightwatchorbugsnag, exceptions are only captured via webhook. Tryexception_listenerfor the simplest setup. - Check your queue worker -- Is it running? Healing-Factor dispatches jobs to the queue. Run
php artisan queue:work --timeout=3700and trigger an error. - Check the logs -- Look at
storage/logs/healing-factor.logfor clues. If the file does not exist, the package may not be receiving exceptions. - Run the test command --
php artisan healing-factor:test --syncbypasses 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.