Guides phased root cause investigation for Laravel issues including test failures, Eloquent queries, bugs, queues, migrations, and performance before fixes.
npx claudepluginhub iserter/laravel-claude-agents --plugin laravel-claude-agentsThis skill uses the workspace's default tool permissions.
Random fixes waste time and create new bugs in Laravel applications. Quick patches mask underlying issues.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Random fixes waste time and create new bugs in Laravel applications. Quick patches mask underlying issues.
Core principle: ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
Violating the letter of this process is violating the spirit of debugging.
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
If you haven't completed Phase 1, you cannot propose fixes.
Use for ANY Laravel technical issue:
Use this ESPECIALLY when:
You MUST complete each phase before proceeding to the next.
BEFORE attempting ANY fix:
Read Error Messages Carefully
SQLSTATE[23000]: Integrity constraint violation
→ Check foreign key constraints, not a code bug
Class 'App\Models\Post' not found
→ Check namespace, run composer dump-autoload
Method Illuminate\Database\Eloquent\Collection::save does not exist
→ get() returns Collection, not Model. Use first() or update()
Check Laravel Logs
# Main Laravel log
tail -f storage/logs/laravel.log
# Check for specific errors
grep "SQLSTATE" storage/logs/laravel.log
# Clear logs if too large
> storage/logs/laravel.log
Enable Debug Mode (Local Only)
APP_DEBUG=true
APP_ENV=local
Use Laravel Telescope
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
# Access at /telescope
# View: Requests, Queries, Jobs, Events, Exceptions
Check Recent Changes
# What changed that could cause this?
git log --oneline -10
git diff HEAD~5
# Check if migrations ran
php artisan migrate:status
# Check if config cached
php artisan config:show
Reproduce Consistently
# Can you trigger it every time?
php artisan tinker
>>> App\Models\Post::first();
# Try in different environments
APP_ENV=testing php artisan test
Trace Data Flow for Eloquent Issues
// Enable query logging
DB::listen(function ($query) {
Log::debug('Query executed', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time' => $query->time,
]);
});
// Or in specific code
DB::enableQueryLog();
$posts = Post::with('user')->get();
dd(DB::getQueryLog());
Find the pattern before fixing:
Find Working Examples in Laravel
# Search for similar working code
grep -r "belongsTo" app/Models/
grep -r "middleware" app/Http/
# Check Laravel docs for the pattern
# Check other models that work correctly
Compare Against Laravel Conventions
// ❌ What you have
class Post extends Model {
public function author() {
return $this->hasOne(User::class, 'id', 'user_id');
}
}
// ✅ Laravel convention
class Post extends Model {
public function user(): BelongsTo {
return $this->belongsTo(User::class);
}
}
Check Laravel Documentation
Identify Differences
// Working model
class User extends Model {
protected $fillable = ['name', 'email'];
}
// Broken model - difference: missing mass assignment protection
class Post extends Model {
// No $fillable or $guarded defined
}
Scientific method:
Form Single Hypothesis
Hypothesis: "Posts aren't saving because mass assignment
protection is blocking the 'user_id' field"
Expected: Adding 'user_id' to $fillable will fix it
Test Minimally
// Before (broken)
protected $fillable = ['title', 'content'];
// Test change (ONE variable)
protected $fillable = ['title', 'content', 'user_id'];
// Don't change multiple things at once
Verify in Tinker
php artisan tinker
>>> $post = Post::create(['title' => 'Test', 'content' => 'Test', 'user_id' => 1]);
>>> $post->user_id; // Should be 1
When You Don't Know
Fix the root cause, not the symptom:
Create Failing Test Case
use Illuminate\Foundation\Testing\RefreshDatabase;
test('user can create post', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/posts', [
'title' => 'Test Post',
'content' => 'Test content',
]);
$response->assertRedirect();
expect(Post::where('title', 'Test Post')->exists())->toBeTrue();
expect(Post::first()->user_id)->toBe($user->id);
});
// Run and watch it FAIL first
php artisan test --filter=user_can_create_post
Implement Single Fix
// ONE fix for the root cause
protected $fillable = ['title', 'content', 'user_id'];
// NO bundled improvements like:
// - Adding casts
// - Refactoring methods
// - Changing other code
Verify Fix
# Test passes now
php artisan test --filter=user_can_create_post
# All tests still pass
php artisan test
# Manual verification
php artisan tinker
>>> $post = Post::create([...]);
If Fix Doesn't Work
If 3+ Fixes Failed: Question Architecture
Pattern indicating architectural problem:
- Each fix reveals new shared state/coupling
- Fixes require "massive refactoring"
- Each fix creates new symptoms elsewhere
STOP and question fundamentals:
- Is this Laravel pattern correct?
- Should we use a different approach (repository/service)?
- Are we fighting the framework?
Discuss with team before attempting more fixes.
// See actual SQL
$posts = Post::where('status', 'published');
dd($posts->toSql(), $posts->getBindings());
// Check relationship loading
$post = Post::first();
$post->relationLoaded('user'); // false
$post->load('user');
$post->relationLoaded('user'); // true
// Prevent lazy loading (catch N+1)
Model::preventLazyLoading(!app()->isProduction());
# List all routes
php artisan route:list
# Find specific route
php artisan route:list --name=posts
# Check route exists
php artisan tinker
>>> route('posts.show', 1);
# See failed jobs
php artisan queue:failed
# Retry failed job
php artisan queue:retry <id>
# Work queue with verbose output
php artisan queue:work --verbose
# Check job payload
php artisan tinker
>>> DB::table('jobs')->first();
// See exact validation errors
protected function failedValidation(Validator $validator)
{
Log::debug('Validation failed', [
'errors' => $validator->errors()->toArray(),
'input' => $this->all(),
]);
parent::failedValidation($validator);
}
If you catch yourself thinking:
ALL of these mean: STOP. Return to Phase 1.
Phase 1: Detect it
- Enable Model::preventLazyLoading()
- Exception thrown showing the problem
Phase 2: Find the pattern
- Check working code that uses with()
- Identify which relationship is lazy loading
Phase 3: Hypothesis
- "Adding with('user') will prevent the N+1"
Phase 4: Fix
- Add test that counts queries
- Add with('user') to the query
- Verify query count reduced
Phase 1: Investigate
- Check route definition: /posts/{post}
- Check controller parameter: Post $post
- Check if using custom key
Phase 2: Pattern
- Compare with working route binding
- Check Post model for getRouteKeyName()
Phase 3: Hypothesis
- "Parameter name doesn't match or model not found"
Phase 4: Fix
- Ensure route parameter matches method parameter
- Or customize: public function getRouteKeyName() { return 'slug'; }
Phase 1: Error says "Add [field] to fillable property"
Phase 2: Check other models' $fillable arrays
Phase 3: Hypothesis: "Field not in $fillable"
Phase 4: Add field to $fillable, test
| Phase | Laravel-Specific Activities | Success Criteria |
|---|---|---|
| 1. Root Cause | Check logs, Telescope, Tinker, recent changes | Understand WHAT and WHY |
| 2. Pattern | Find working Laravel examples, check docs | Identify differences |
| 3. Hypothesis | Form theory, test in Tinker | Confirmed or new hypothesis |
| 4. Implementation | Create Pest test, fix, verify | Bug resolved, tests pass |
Always investigate systematically, understand the root cause, then fix once correctly.