Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
iserter avatar

Laravel Systematic Debugging

  • 75 installs
  • 42 repo stars
  • Updated April 19, 2026
  • iserter/laravel-claude-agents

Debug Laravel issues by finding root cause before attempting fixes, covering test failures, Eloquent, auth, queues, and N+1 problems.

About

Enforces a root-cause-first debugging process for Laravel test failures, bugs, and performance problems, forbidding fixes before investigation. A developer uses it for any Laravel issue, especially under time pressure.

  • Iron Law: no fixes without root-cause investigation first
  • Covers Eloquent, auth, queue, route, migration, and N+1 issues

Laravel Systematic Debugging by the numbers

  • 75 all-time installs (skills.sh)
  • +3 installs in the week ending Aug 2, 2026 (Skillselion tracking)
  • Ranked #262 of 596 Debugging skills by installs in the Skillselion catalog
  • Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/iserter/laravel-claude-agents --skill laravel-systematic-debugging

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs75
repo stars42
Last updatedApril 19, 2026
Repositoryiserter/laravel-claude-agents

What it does

Debug Laravel issues by finding root cause before attempting fixes, covering test failures, Eloquent, auth, queues, and N+1 problems.

Files

SKILL.mdMarkdownGitHub ↗

Systematic Debugging for Laravel

Overview

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.

The Iron Law

NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST

If you haven't completed Phase 1, you cannot propose fixes.

When to Use

Use for ANY Laravel technical issue:

  • Test failures
  • Eloquent query issues
  • Authentication/authorization bugs
  • Validation failures
  • Queue job failures
  • Route errors
  • Migration issues
  • N+1 query problems
  • Performance issues

Use this ESPECIALLY when:

  • Under time pressure
  • "Just one quick fix" seems obvious
  • You've already tried multiple fixes
  • Previous fix didn't work
  • You don't fully understand the issue

The Four Phases

You MUST complete each phase before proceeding to the next.

Phase 1: Root Cause Investigation

BEFORE attempting ANY fix:

1. 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()

2. 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

3. Enable Debug Mode (Local Only)

   APP_DEBUG=true
   APP_ENV=local

4. Use Laravel Telescope

   composer require laravel/telescope --dev
   php artisan telescope:install
   php artisan migrate
   
   # Access at /telescope
   # View: Requests, Queries, Jobs, Events, Exceptions

5. 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

6. 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

7. 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());

Phase 2: Pattern Analysis

Find the pattern before fixing:

1. 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

2. 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);
       }
   }

3. Check Laravel Documentation

  • Read the COMPLETE section, don't skim
  • Follow examples exactly first
  • Customize only after understanding

4. 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
   }

Phase 3: Hypothesis and Testing

Scientific method:

1. 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

2. 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

3. Verify in Tinker

   php artisan tinker
   >>> $post = Post::create(['title' => 'Test', 'content' => 'Test', 'user_id' => 1]);
   >>> $post->user_id; // Should be 1

4. When You Don't Know

  • Say "I don't understand why X is happening"
  • Check Laravel GitHub issues for similar problems
  • Ask in Laravel Discord/Forums with specifics
  • Don't pretend to know

Phase 4: Implementation

Fix the root cause, not the symptom:

1. 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

2. 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

3. 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([...]);

4. If Fix Doesn't Work

  • STOP
  • Count: How many fixes have you tried?
  • If < 3: Return to Phase 1 with new information
  • If ≥ 3: STOP and question the approach

5. 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.

Laravel-Specific Debug Techniques

Eloquent Debugging

// 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());

Route Debugging

# 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);

Queue Debugging

# 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();

Validation Debugging

// See exact validation errors
protected function failedValidation(Validator $validator)
{
    Log::debug('Validation failed', [
        'errors' => $validator->errors()->toArray(),
        'input' => $this->all(),
    ]);
    
    parent::failedValidation($validator);
}

Red Flags - STOP and Follow Process

If you catch yourself thinking:

  • "Quick fix for now, investigate later"
  • "Just try changing X and see if it works"
  • "Add protected $guarded = [] to see if that helps"
  • "Skip the test, I'll manually verify"
  • "It's probably the relationship definition"
  • "I don't fully understand Eloquent but this might work"
  • "The docs say X but I'll adapt it differently"
  • "One more fix attempt" (when already tried 2+)

ALL of these mean: STOP. Return to Phase 1.

Common Laravel Debugging Scenarios

Scenario 1: N+1 Query Problem

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

Scenario 2: Route Model Binding Not Working

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'; }

Scenario 3: Mass Assignment Exception

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

Integration with Laravel Agents

  • Use laravel-debugger for Laravel-specific debugging help
  • Use laravel-testing-expert for creating failing tests (Phase 4)
  • Use eloquent-specialist for relationship debugging
  • Use laravel-performance-optimizer for performance issues

Quick Reference

PhaseLaravel-Specific ActivitiesSuccess Criteria
1. Root CauseCheck logs, Telescope, Tinker, recent changesUnderstand WHAT and WHY
2. PatternFind working Laravel examples, check docsIdentify differences
3. HypothesisForm theory, test in TinkerConfirmed or new hypothesis
4. ImplementationCreate Pest test, fix, verifyBug resolved, tests pass

Remember

  • Laravel has excellent error messages - read them fully
  • Use Telescope for comprehensive debugging
  • Tinker is your friend for testing hypotheses
  • Follow Laravel conventions - fighting the framework causes bugs
  • 95% of "weird Laravel behavior" is misunderstanding the framework

Always investigate systematically, understand the root cause, then fix once correctly.

Related skills

Debuggingbackend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.