Semi-autonomous error resolution for local development. Autopilot monitors Laravel logs and frontend errors, then dispatches Claude CLI agents to fix issues while you keep testing.
- PHP 8.4+
- Laravel 12+
- Claude CLI available in
PATH
composer require romegasoftware/laravel-autopilot --devEnable in your .env:
AUTOPILOT_ENABLED=true
VITE_AUTOPILOT_ENABLED=true
Publish the config (optional):
php artisan vendor:publish --tag=autopilot-configRun the watcher standalone:
php artisan autopilot:watchOr integrate into your dev script:
{
"scripts": {
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"npm run dev\" \"php artisan autopilot:watch\" --names=server,vite,autopilot"
]
}
}Autopilot captures frontend errors via a small client library. The package provides TypeScript modules you import directly.
In your main entry file (e.g., resources/js/app.tsx):
const shouldEnableAutopilot =
import.meta.env.DEV && import.meta.env.VITE_AUTOPILOT_ENABLED === 'true';
if (shouldEnableAutopilot) {
import('laravel-autopilot/autopilot-client').then(({ initAutopilot }) => {
initAutopilot();
});
}Wrap your app in the error boundary to capture React component errors:
import { AutopilotErrorBoundary } from 'laravel-autopilot/AutopilotErrorBoundary';
const shouldEnableAutopilot =
import.meta.env.DEV && import.meta.env.VITE_AUTOPILOT_ENABLED === 'true';
// Passthrough component when autopilot is disabled
const Passthrough = ({ children }: { children: ReactNode }) => <>{children}</>;
const AutopilotBoundary = shouldEnableAutopilot ? AutopilotErrorBoundary : Passthrough;
createInertiaApp({
setup({ el, App, props }) {
createRoot(el).render(
<AutopilotBoundary>
<App {...props} />
</AutopilotBoundary>
);
},
});Custom fallback UI:
<AutopilotErrorBoundary
fallback={<div className="p-4 bg-red-100">Something broke!</div>}
>
<App {...props} />
</AutopilotErrorBoundary>Install the interceptor on your Axios instance to capture validation errors:
// resources/js/bootstrap.js
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
if (import.meta.env.DEV && import.meta.env.VITE_AUTOPILOT_ENABLED === 'true') {
import('laravel-autopilot/axios-interceptor').then(({ installAutopilotInterceptor }) => {
installAutopilotInterceptor(window.axios);
});
}Report errors manually when needed:
import { reportError } from 'laravel-autopilot/autopilot-client';
try {
await riskyOperation();
} catch (error) {
reportError('Custom operation failed', {
stack: error.stack,
url: window.location.href,
});
}Add the package alias to your vite.config.ts:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/js/app.tsx'],
refresh: true,
}),
],
resolve: {
alias: {
'laravel-autopilot': '/vendor/romegasoftware/laravel-autopilot/resources/js',
},
},
});Full configuration options in config/autopilot.php:
<?php
return [
// Master toggle
'enabled' => env('AUTOPILOT_ENABLED', false),
// Claude CLI settings
'claude' => [
'binary' => env('AUTOPILOT_CLAUDE_BINARY', 'claude'),
'model' => env('AUTOPILOT_CLAUDE_MODEL'),
'timeout' => env('AUTOPILOT_TIMEOUT', 300),
'max_parallel_agents' => env('AUTOPILOT_MAX_AGENTS', 3),
'path' => env('AUTOPILOT_CLAUDE_PATH'),
],
// Log monitoring
'logs' => [
'path' => storage_path('logs/laravel.log'),
'levels' => ['error', 'critical', 'alert', 'emergency'],
'poll_interval_ms' => 500,
],
// Frontend error capture
'frontend' => [
'enabled' => true,
'capture_react_errors' => true,
'capture_console_errors' => true,
'capture_unhandled_rejections' => true,
],
// 422 validation error handling
'validation_errors' => [
'enabled' => env('AUTOPILOT_CAPTURE_422', true),
'include_request_data' => true,
'ignore_fields' => ['password', 'password_confirmation', 'credit_card', 'cvv'],
],
// Error filtering
'ignore' => [
'patterns' => [
'/Undefined array key.*session/',
'/CSRF token mismatch/',
],
'exceptions' => [
Illuminate\Auth\AuthenticationException::class,
Illuminate\Session\TokenMismatchException::class,
Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class,
],
'paths' => [
'vendor/',
'node_modules/',
],
],
// Deduplication
'deduplication' => [
'ttl' => 3600,
'store_path' => storage_path('logs/autopilot_seen_errors.json'),
'include_stack_trace' => false,
],
// Cooldown to prevent agent spam
'cooldown' => [
'after_dispatch' => 5,
'after_completion' => 30,
'max_attempts' => 3,
],
// HMR protection (prevents errors during Vite hot reload)
'hmr_protection' => [
'cooldown_after_file_change' => 5,
'frontend_cooldown_after_completion' => 10,
'ignore_patterns' => [
'/\[vite\].*hot update/',
'/\[HMR\]/',
'/ChunkLoadError/',
'/Loading chunk.*failed/',
'/Failed to fetch dynamically imported module/',
],
'lock_modified_files' => true,
],
// Context included with errors
'context' => [
'include_route_name' => true,
'include_controller' => true,
'max_stack_frames' => 10,
],
// Output settings
'output' => [
'stream_agent_output' => true,
'agent_output_prefix' => '[agent-%d] ',
],
// Customize agent prompts
'prompts' => [
'prefix' => <<<'PROMPT'
You are fixing an error in a Laravel application during local development.
IMPORTANT CONTEXT:
- This is a Laravel 12 / Inertia v2 / React 19 application
- Follow existing code patterns in the codebase
- Run tests after making changes to verify the fix
- Keep changes minimal and focused on the error
PROMPT,
'suffix' => <<<'PROMPT'
After fixing, verify your changes work by:
1. Running relevant tests if they exist
2. Checking for TypeScript errors with `npm run typecheck` if you modified frontend code
PROMPT,
],
];-
Backend errors: The watcher tails
storage/logs/laravel.logand detects new errors based on configured severity levels. -
Frontend errors: The client captures:
- React component errors (via error boundary)
- Console errors (
console.error) - Unhandled promise rejections
- 422 validation errors (via Axios interceptor)
-
Dispatching agents: When an error is detected, Autopilot:
- Checks deduplication cache to avoid re-processing
- Applies ignore patterns and exception filters
- Respects cooldown periods
- Spawns a Claude CLI agent with error context
-
HMR protection: The client ignores errors that occur during Vite hot module replacement to prevent false positives when agents modify files.
| Variable | Default | Description |
|---|---|---|
AUTOPILOT_ENABLED |
false |
Master toggle for backend |
VITE_AUTOPILOT_ENABLED |
false |
Master toggle for frontend |
AUTOPILOT_CLAUDE_BINARY |
claude |
Path to Claude CLI |
AUTOPILOT_CLAUDE_MODEL |
(default) | Override Claude model |
AUTOPILOT_TIMEOUT |
300 |
Agent timeout in seconds |
AUTOPILOT_MAX_AGENTS |
3 |
Max parallel agents |
AUTOPILOT_CLAUDE_PATH |
- | Prepend to PATH for node/claude resolution |
AUTOPILOT_CAPTURE_422 |
true |
Capture validation errors |
Autopilot stores its logs and error tracking in storage/logs/:
| File | Purpose |
|---|---|
storage/logs/autopilot.log |
Autopilot activity log (agent dispatches, completions, errors) |
storage/logs/autopilot_seen_errors.json |
Tracks error signatures to prevent duplicate agent dispatches |
If an agent failed to fix an error and you want to retry, clear the error from the seen errors cache:
# Clear all seen errors (retrigger everything)
rm storage/logs/autopilot_seen_errors.json
# Or edit the JSON to remove specific entries
cat storage/logs/autopilot_seen_errors.jsonThe JSON file uses hashed error signatures:
{
"seen": {
"e5be2f2968f8b135": 1770226514,
"745eee2a90c95b32": 1770226536
},
"attempts": {
"e5be2f2968f8b135": 1,
"745eee2a90c95b32": 2
},
"last_updated": "2026-02-04T18:46:38+00:00"
}seen: Hash → Unix timestamp of first occurrenceattempts: Hash → Number of agent dispatch attempts
Remove a hash from both seen and attempts to allow that error to trigger a new agent.
- Autopilot only registers when
APP_ENV=localandAUTOPILOT_ENABLED=true - Frontend client only activates when
import.meta.env.DEVis true - Errors are deduplicated within a session to prevent agent spam
- The watcher gracefully handles log rotation