Laravel + Claude Code in 2026: How I Let an AI Agent Write, Test, and Fix My Laravel Features While I Reviewed Pull Requests

Claude Code on Opus 5 just became the top-rated terminal AI coding agent. Here is exactly how to set it up for a Laravel project, which tasks it handles without supervision, which ones need your eyes, and the Pest test suite configuration that makes agentic development safe enough to use on production code.


Claude Opus 5 landed on July 24, 2026 and changed the economics of agentic development. It scores 96.0% on SWE-bench Verified, leads Fable 5 on Frontier-Bench agentic coding (43.3% vs 33.7%), has a 1M-token context window as the default, and costs $5/$25 per million tokens — half the price of Fable 5, the same price as Opus 4.8. The model that was already the best agentic coding option got meaningfully better at the same price point.

The workflow this enables: Claude Code reads the Laravel codebase in full context, builds features from a specification, writes Pest tests against them, runs the test suite, reads the failures, and fixes them — iteratively, without supervision. You review a diff when it’s done. The question isn’t whether this is possible. It’s which tasks are safe to run autonomously and which need you in the loop — and exactly how to configure the test suite so that autonomous fixes can’t quietly break things that were already working.

This post documents the exact setup, the task taxonomy, and the Pest configuration that makes it work safely.


Installing and Configuring Claude Code for Laravel

# Install Claude Code (requires Node.js 18+)
npm install -g @anthropic-ai/claude-code

# Authenticate
claude auth

# Navigate to your Laravel project
cd /var/www/my-laravel-app

# Start a session with Opus 5
claude --model claude-opus-5

The model flag selects Opus 5 explicitly. Without it, Claude Code uses its default model. For most agentic Laravel work, Opus 5 is the right default — it handles multi-file features, large refactors, and long agentic loops without losing context.

Setting the effort level:

# Low effort — fast, cheap, for mechanical tasks
claude --model claude-opus-5 --effort low

# Medium effort — default, most feature work
claude --model claude-opus-5 --effort medium

# High effort — complex debugging, architecture decisions
claude --model claude-opus-5 --effort high

Effort level controls how much test-time compute Opus 5 uses. For a routine CRUD feature: medium. For debugging a race condition in the queue: high. For generating a boilerplate controller: low. Matching effort to task is the single biggest cost lever in a Claude Code session.

Fast mode for interactive loops:

# Fast mode: ~2.5x speed, 2x the price ($10/$50 per million)
claude --model claude-opus-5 --fast

Fast mode is worth it when you’re iterating quickly — short tasks where the latency reduction matters more than the price difference.


The CLAUDE.md File — Project Context the Agent Reads First

Every Claude Code session reads CLAUDE.md in the project root before doing anything. This file is where Laravel-specific context lives. Without it, the agent makes general PHP/Laravel decisions rather than project-specific ones.

# CLAUDE.md

## Project: [Your SaaS Name]

### Stack
- Laravel 13, PHP 8.4
- Livewire 4 for UI components
- Pest 3 for testing
- Inertia.js + Vue 3 for dashboard pages
- MySQL 8 (not SQLite — all queries must be MySQL-compatible)
- Redis for cache, queues, and sessions
- Spatie Laravel Permission for roles/permissions
- Laravel Cashier for Stripe billing

### Multi-Tenancy
This is a single-database multi-tenant SaaS.
- The BelongsToTenant trait adds a global Eloquent scope
- Every tenant-scoped model uses this trait
- NEVER use ::find() or ::findOrFail() without tenant scoping
- ALWAYS scope queries to the current tenant via the trait or explicit where clause

### Testing
- All tests use Pest 3 syntax (it() and describe(), not class-based)
- Test database: MySQL (same as production — not SQLite)
- Factories exist for all models in database/factories/
- Every new feature requires tests before the PR is ready
- Run: php artisan test --parallel

### Code Style
- PHP 8.4 syntax (readonly properties, match expressions, named arguments)
- Form Requests for all controller input validation
- API Resources for all JSON responses
- Typed properties and return types on all methods

### What NOT to Do
- No $guarded = [] on models — always define $fillable explicitly
- No inline validation in controllers — use Form Requests
- No direct Stripe API calls — use the BillingService wrapper
- No env() calls in application code — use config()
- No ::all() without pagination on collections larger than 100 rows
- No raw SQL — use Eloquent or the query builder

### Running the Application
```bash
php artisan serve           # Local server
php artisan test --parallel # Run all tests in parallel
php artisan horizon         # Queue workers

Key Files

  • app/Traits/BelongsToTenant.php ← read this before touching any model
  • app/Services/BillingService.php ← wrapper for all Stripe operations
  • app/Exceptions/Handler.php ← error response format
  • config/billing.php ← plan configuration

The CLAUDE.md is the project's contract with the agent. Every rule that would otherwise require human review to catch — tenant scoping, MySQL compatibility, Form Request usage — is stated here. The agent reads it on every session start. When the agent violates a rule, you update CLAUDE.md to make the rule clearer.

---

## The Pest Configuration That Makes Agentic Development Safe

The agent's self-verification loop only works if the test suite is a reliable gate. A test suite that can be silently gamed (tests that pass on wrong behaviour, tests that use SQLite while production uses MySQL, tests with no database state assertions) isn't a safety net.

**The database configuration — MySQL in tests, not SQLite:**

```php
// phpunit.xml / pestPHP.xml
<php>
    <env name="DB_CONNECTION" value="mysql"/>
    <env name="DB_DATABASE" value="laravel_test"/>
    <env name="DB_HOST" value="127.0.0.1"/>
    <env name="DB_PORT" value="3306"/>
    <env name="DB_USERNAME" value="root"/>
    <env name="DB_PASSWORD" value=""/>
    <!-- NOT sqlite — MySQL-incompatible queries must fail in tests -->
</php>
// config/database.php — test connection
'mysql_test' => [
    'driver'   => 'mysql',
    'host'     => env('DB_HOST', '127.0.0.1'),
    'database' => env('DB_DATABASE', 'laravel_test'),
    // ... same as production connection
],

SQLite in tests is the most common gap between test and production behaviour. GROUP BY strict mode, JSON column syntax, case-sensitive LIKE — all differ. When the agent fixes a MySQL-incompatible query, it needs the test to fail on that query, not silently pass because SQLite is more permissive.

The Pest base test setup:

// tests/Pest.php
uses(
    Tests\TestCase::class,
    Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature');

uses(
    Tests\TestCase::class,
)->in('Unit');

// Custom expectations for common patterns
expect()->extend('toBeValidJson', function () {
    json_decode($this->value);
    expect(json_last_error())->toBe(JSON_ERROR_NONE);
    return $this;
});

expect()->extend('toHaveApiErrorShape', function () {
    return $this->toHaveKeys(['message', 'code', 'trace_id']);
});

The test categories the agent must produce:

// Every feature the agent builds must have tests in these categories:

// 1. Happy path — the feature works
it('creates a project for the authenticated tenant', function () {
    $user    = User::factory()->create();
    $project = actingAs($user)->postJson('/api/projects', [
        'name' => 'Test Project',
        'type' => 'software',
    ])->assertCreated()->json('data');

    expect(Project::find($project['id'])->tenant_id)
        ->toBe($user->current_tenant_id);
});

// 2. Tenant isolation — other tenants can't access this data
it('returns 404 for projects belonging to other tenants', function () {
    $user         = User::factory()->create();
    $otherTenant  = Tenant::factory()->create();
    $otherProject = Project::factory()->for($otherTenant)->create();

    actingAs($user)
        ->getJson("/api/projects/{$otherProject->id}")
        ->assertNotFound();
});

// 3. Validation — invalid input is rejected
it('rejects project creation with missing required fields', function () {
    $user = User::factory()->create();

    actingAs($user)
        ->postJson('/api/projects', [])
        ->assertUnprocessable()
        ->assertJsonPath('code', 'VALIDATION_ERROR')
        ->assertJsonStructure(['message', 'code', 'errors', 'trace_id']);
});

// 4. Authorization — unauthenticated requests are rejected
it('returns 401 for unauthenticated project requests', function () {
    Project::factory()->create();

    getJson('/api/projects')
        ->assertUnauthorized()
        ->assertJsonPath('code', 'UNAUTHENTICATED');
});

// 5. Database state — the right data is persisted
it('persists all provided project fields to the database', function () {
    $user = User::factory()->create();

    actingAs($user)->postJson('/api/projects', [
        'name'        => 'My Project',
        'description' => 'A detailed description',
        'type'        => 'software',
    ])->assertCreated();

    assertDatabaseHas('projects', [
        'name'      => 'My Project',
        'type'      => 'software',
        'tenant_id' => $user->current_tenant_id,
    ]);
});

The agent is instructed to produce all five categories for every feature. If it produces only a happy-path test, the reviewer knows the test coverage is incomplete before merging.

The parallel test configuration:

# composer.json scripts
"scripts": {
    "test": "php artisan test --parallel",
    "test:coverage": "php artisan test --parallel --coverage --min=80"
}

Parallel tests are essential for agentic development — the agent runs the suite multiple times per feature (after writing the initial implementation, after each fix). If the suite takes 4 minutes serially, the agent spends 20 minutes waiting across a 5-fix loop. Parallel execution with --parallel (using ParaTest under the hood) reduces this significantly.


The Task Taxonomy — What to Hand Off vs What to Keep

Not all Laravel tasks are equal candidates for agentic development. The taxonomy below is based on six months of running Claude Code on production Laravel codebases with Opus 4.8 and Opus 5.

Tier 1: Hand Off Without Supervision

These tasks produce reliable output with minimal risk. The agent handles them end-to-end. You review the diff.

CRUD features from a specification:

Prompt: "Build a tags feature for projects.
- Tags belong to tenants (use BelongsToTenant trait)
- Projects have many tags via a pivot table
- API endpoints: GET /api/tags, POST /api/tags,
  DELETE /api/tags/{id}
- Tags can be attached to projects: POST /api/projects/{id}/tags,
  DELETE /api/projects/{id}/tags/{tagId}
- Validation: tag name required, min:2, max:50, unique per tenant
- Pest tests: all five categories (happy path, tenant isolation,
  validation, authorization, database state)
- API Resource for tag responses
- Form Requests for all input
- Migration with proper indexes"

The agent reads CLAUDE.md, reads the existing Tag-adjacent code, generates the migration, model, Form Requests, API Resource, controller, routes, and Pest tests. It runs the tests, reads any failures, fixes them. When the tests pass, it commits.

Review focus: does it use BelongsToTenant? Does the tenant isolation test pass? Is $fillable defined? Are the indexes on the migration right?

Model observers and event listeners:

The agent handles these reliably because they have a defined pattern (method name maps to Eloquent event, listener class maps to event class) and a clear test surface (dispatch the event, assert the listener ran, assert the side effect).

Artisan commands:

Commands with --dry-run flags, data migration commands, reporting commands. The agent generates the command, the service class it delegates to, and tests that assert the command’s output and database state.

API resource transformation:

Given a model and a specification of the JSON shape, the agent generates the Resource class, handles whenLoaded() for relationships, formats dates correctly, and writes tests that assert the shape.

Form Request validation classes:

Given a list of fields and their validation requirements, the agent generates the Form Request, adds custom error messages, writes tests that assert validation errors for each invalid input.

Tier 2: Hand Off with a Specification Review

These tasks the agent handles well but require you to review the specification before starting, because an incorrect specification produces correct code for the wrong thing.

Service classes with business logic:

The agent implements what you specify. Business logic is specification-dependent — if your specification of the discount application flow is incomplete, the agent’s implementation will be incomplete in exactly the ways your specification was.

Review discipline: write the specification as if you’re writing the test cases. Every edge case you describe in the specification is an edge case the agent will handle. Every one you omit is one it won’t.

Database migrations for schema changes:

The agent generates migrations correctly but the schema decision itself is yours. Should status be an enum or a string? Should the foreign key cascade or restrict? Should this be a separate table or a JSON column? These decisions need human judgment before the agent generates anything.

Webhook handlers:

The agent handles the verification and dispatch correctly but the business logic triggered by each event requires a specification. “When invoice.payment_failed fires, mark the tenant as past_due and send an email” is a specification. The agent implements it. Whether marking past_due is the right response for a first payment failure vs a third requires your judgment.

Complex Eloquent queries:

The agent generates the query from a description of what data it should return. For simple queries this is straightforward. For complex queries involving multiple joins, subqueries, or window functions, review the generated SQL with EXPLAIN before deploying.

Tier 3: Keep in the Loop

These tasks require ongoing human judgment. The agent can assist but shouldn’t run autonomously.

Security-related features:

Authentication flows, permission checks, API token generation, rate limiting configuration. The agent implements these correctly in the happy path but security requires adversarial thinking — imagining what happens when a malicious user sends unexpected input. Review every line of security-adjacent code yourself.

Database schema design:

The shape of the schema determines the application’s flexibility for years. The agent can suggest schema designs but the decision requires understanding of future requirements that aren’t in the codebase.

Performance optimization:

The agent identifies and fixes N+1 queries reliably. But database index selection, query planner decisions, and cache strategy require understanding of production data distribution that the agent doesn’t have access to.

Deployment configuration:

Supervisor configuration for Horizon, Nginx configuration for Reverb, auto-scaling policies — these affect production reliability. Don’t let the agent touch deployment configuration autonomously.


The Agentic Workflow in Practice

The session prompt pattern:

You are working on a Laravel 13 multi-tenant SaaS.
Read CLAUDE.md for project rules.

Task: [specific feature description]

Requirements:
- [list each requirement explicitly]

Acceptance criteria (these must all be true before you're done):
- All Pest tests pass (php artisan test --parallel)
- The five test categories are covered: happy path, tenant isolation,
  validation, authorization, database state
- No $guarded = [] anywhere in new code
- No ::findOrFail() without tenant scoping
- All inputs go through Form Requests
- All responses go through API Resources

When you're finished, run the test suite one final time and
report the test count and any remaining failures.

The acceptance criteria in the prompt are the automated gate. The agent verifies them by running the test suite. A test suite that fails means the agent isn’t done — it continues fixing until the criteria are met.

Watching an agentic loop:

A typical feature loop looks like this:

[Agent reads CLAUDE.md]
[Agent reads existing models, migrations, routes]
[Agent generates migration]
[Agent generates model with BelongsToTenant trait]
[Agent generates Form Request]
[Agent generates API Resource]
[Agent generates controller]
[Agent adds routes]
[Agent writes Pest tests]
[Agent runs: php artisan test --parallel]

FAIL  Tests\Feature\Tags\TagControllerTest
  ✗ it creates a tag for the authenticated tenant
      Expected response status 201 but received 500.
     [stack trace: column 'tenant_id' cannot be null]

[Agent reads the error]
[Agent checks the model — missing BelongsToTenant trait on Tag model]
[Agent adds the trait]
[Agent runs: php artisan test --parallel]

PASS  Tests\Feature\Tags\TagControllerTest
  ✓ it creates a tag for the authenticated tenant [45ms]
  ✓ it returns 404 for tags belonging to other tenants [23ms]
  ✓ it rejects tag creation with missing fields [18ms]
  ✓ it returns 401 for unauthenticated requests [12ms]
  ✓ it persists tag fields to the database [31ms]

Tests: 5 passed
      

The agent found and fixed the BelongsToTenant omission itself — because the test for tenant isolation failed when the tag was created without a tenant_id. The test was the gate. The gate worked.

The diff review checklist:

When the agent completes a task and you review the diff:

□ Does every new model have BelongsToTenant (or an explicit reason not to)?
□ Is $fillable defined and narrow?
□ Do all controller methods scope queries to the current tenant?
□ Is all input going through Form Requests?
□ Are all responses going through API Resources?
□ Does the migration have indexes on queried columns?
□ Are there tests for tenant isolation (the most important category)?
□ Do the tests assert database state, not just response status?

This checklist takes about four minutes. It’s faster than writing the code. The quality of the review is higher than the quality of the code because you’re reading finished code rather than writing it.


Effort Levels in Practice

The effort level setting is Opus 5’s most underutilized configuration for Laravel work.

# Low effort — mechanical tasks
# Generate a migration for a new table
# Add a column to an existing migration
# Generate a factory for an existing model
# Add a route to routes/api.php
claude --model claude-opus-5 --effort low "Add a migration for the project_tags pivot table with project_id, tag_id, and a composite primary key"

# Medium effort — feature work
# New CRUD feature end-to-end
# Add a new API endpoint with validation and tests
# Refactor a service class
claude --model claude-opus-5 --effort medium "Build the tags feature as described in the CLAUDE.md"

# High effort — complex debugging
# Race condition in a queued job
# Performance bottleneck in a complex query
# Multi-step billing edge case
claude --model claude-opus-5 --effort high "Debug why the subscription webhook handler is processing duplicate events despite the idempotency cache"

Effort scaling matters because test-time compute costs real money. A low-effort session for a migration is $0.05. A high-effort session for a complex debugging task might be $2.00. For a team running 20 agentic sessions per day, the difference between always using high effort and matching effort to task is material.


The 1M Context Window for Large Laravel Projects

Opus 5’s 1M-token context window means entire Laravel projects fit in context. In practice this changes what you can ask the agent to do:

# Before 1M context: you had to point the agent at specific files
"Read app/Services/BillingService.php and write tests for the subscribe() method"

# With 1M context: the agent can read the whole codebase
"Find all the places in the codebase where we make Stripe API calls
and verify each one handles the IncompletePayment exception correctly.
Write tests for any that don't."

The second prompt type is the one that changes how agentic development works. Cross-cutting tasks — verifying a security pattern across all controllers, finding all N+1 queries across all views, ensuring all new models have proper indexes in their migrations — now happen in one session rather than requiring careful file curation.

For a Laravel project of typical size (200–400 PHP files), the entire codebase fits comfortably in the 1M-token window. The agent reads everything, reasons across the full context, and produces changes that are consistent with the existing patterns rather than just the files you happened to point at.


Running the Workflow in CI

The agentic workflow integrates with CI:

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: laravel_test
        ports:
          - 3306:3306

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          extensions: pdo_mysql, redis

      - name: Install dependencies
        run: composer install --no-interaction

      - name: Run Pest tests
        run: php artisan test --parallel
        env:
          DB_CONNECTION: mysql
          DB_HOST: 127.0.0.1
          DB_PORT: 3306
          DB_DATABASE: laravel_test
          DB_USERNAME: root
          DB_PASSWORD: password

The CI runs the same test suite the agent runs locally. When the agent submits a PR, CI runs the tests again. If they pass locally for the agent and fail in CI, it’s usually an environment difference (SQLite local vs MySQL CI, different env variables). The configuration above ensures CI uses MySQL — matching the agent’s local environment.


What This Changed About the Development Workflow

Six months in, the clearest change: the ratio of time spent writing code vs reviewing code shifted significantly. Features that previously took a day to write and an hour to review now take an hour of specification writing, 20 minutes of agentic generation (running autonomously), and 30 minutes of review. The total time is similar for simple features. The developer’s time is spent on specification and review rather than implementation.

The quality difference: the agent consistently writes tests that cover cases human developers skip when tired or under deadline. The tenant isolation test and the database state test appear in every agentic feature. They appeared inconsistently in human-written features. When a test catches a bug that would have reached production — and it has, several times — the value of the agentic test suite isn’t the speed of writing the code. It’s the consistency of the safety net.

The workflow isn’t “AI replaces the developer.” It’s “developer spends time on specification, architecture, and review — the parts that require genuine judgment — and the agent handles implementation, testing, and iteration.” The split is what it should have been all along.

Leave a Reply

Your email address will not be published. Required fields are marked *