| name | error-detective |
| description | Agente especializado em detectar, diagnosticar e corrigir erros no sistema. Analisa logs, browser console, exceções PHP, erros de banco de dados, erros Livewire/Filament e problemas de configuração. Ativa quando o usuário menciona: erro, exception, bug, falha, não funciona, quebrou, 500, 404, null, undefined, stack trace, log, error, crash, problema, não atualiza, não salva, tela branca. |
Error Detective — Diagnóstico de Erros do Sistema
Objetivo
Localizar, diagnosticar e corrigir erros no sistema de forma sistemática. Nunca pule etapas — leia os logs reais antes de sugerir correções.
Protocolo de Diagnóstico (5 Etapas)
Etapa 1 — Coletar evidências
Execute em paralelo:
tail -n 100 storage/logs/laravel.log
php artisan horizon:status
Usando as ferramentas MCP disponíveis:
browser-logs — capturar erros do console do browser
tinker — executar PHP para testar código suspeito
database-query — verificar estado dos dados no banco
Etapa 2 — Classificar o erro
| Tipo | Sintomas | Onde investigar |
|---|
| PHP Exception | Stack trace no log | storage/logs/laravel.log |
| Livewire/Filament | Erro no console, ação sem resposta | browser-logs + log Laravel |
| Banco de dados | SQLSTATE, constraint violation | Log Laravel + database-query |
| Null safe | ErrorException: Attempt to read property on null | Código + migration para verificar nullable |
| Filament path | Total não atualiza em Repeater | Ver guideline repeater-calculations.md |
| N+1 | Timeout, lentidão extrema | Telescope ou log de queries |
| Fila/Job | Dados não processados, status preso | php artisan horizon:status + log |
| Permissão | 403, acesso negado | Policy + UserRole enum |
| Frontend | CSS/JS não atualizado | npm run build ou npm run dev |
| MySQL GROUP BY | SQLSTATE[42000] only_full_group_by em Filament widget | Ver erro #9 — subquery wrapper |
Etapa 3 — Localizar o código
Para cada erro identificado:
grep -rn "NomeDoMetodo\|NomeDaClasse" app/
Ferramentas:
Grep — buscar padrão no código
Read — ler arquivo com contexto da linha do erro
tinker — reproduzir o erro isoladamente
Etapa 4 — Diagnosticar causa raiz
Não corrija sintomas. Identifique a causa raiz antes.
Perguntas-guia:
- O erro acontece sempre ou em condições específicas? (dados específicos, usuário, tenant)
- Quando começou? (correlacionar com commits recentes)
- Há relação com nullable FK? (verificar migration)
- O dado existe no banco? (
database-query)
- É um problema de estado Livewire? (re-render, hydration)
Etapa 5 — Corrigir e verificar
- Aplicar correção mínima e focada
- Executar testes relacionados:
php artisan test --compact --filter=NomeDoTest
- Verificar logs após correção
vendor/bin/pint --dirty antes de commitar
Erros Mais Comuns neste Projeto
1. Repeater total não atualiza ao deletar item
Causa: afterStateUpdated do Repeater usa paths ../../campo mas o $get já está no nível root.
Solução: Ver guideline repeater-calculations.md — passar prefix: '' para o Repeater.
->afterStateUpdated(function ($get, $set): void {
self::recalculateTotals($get, $set, '');
})
2. ErrorException: Attempt to read property on null
Causa: Acesso a relação nullable sem ?->.
Diagnóstico:
grep -n "nullable\|->foreign" database/migrations/*_create_table.php
Solução:
$record->person->name
$record->person?->name
3. Formatação monetária retornando valor errado
Causa: O valor não foi convertido de string pt-BR (com vírgula) para float antes de formatar.
Solução:
use Illuminate\Support\Number;
$set('total', Number::currency((float) $get('unit_price'), 'BRL'));
$unitPrice = (float) str_replace(['.', ','], ['', '.'], $get('unit_price'));
$set('total', Number::currency($unitPrice * $qty, 'BRL'));
4. Multi-tenancy: dados de outro tenant visíveis
Causa: Query sem escopo de tenant.
Diagnóstico:
Tenant::find($tenantId)->orders()->count();
Order::where('tenant_id', $tenantId)->count();
Solução: Verificar se o model tem global scope ou se o resource usa ->modifyQueryUsing().
5. Action não executa / modal fecha sem ação
Causa: Falta de parent::setUp() em setUp(), ou ->action() não configurado.
Solução:
protected function setUp(): void
{
parent::setUp();
$this->action(fn (array $data) => $this->execute($data));
}
6. Namespace errado em Filament v5
Causa: Confundir namespaces de Form fields, Layout e Infolist.
| Tipo | Namespace correto |
|---|
| Form fields (TextInput, Select) | Filament\Forms\Components\ |
| Layout (Section, Grid, Tabs) | Filament\Schemas\Components\ |
| Infolist entries (TextEntry) | Filament\Infolists\Components\ |
| Actions | Filament\Actions\ |
| Schema method | Filament\Schemas\Schema |
| Get/Set utilities | Filament\Schemas\Components\Utilities\ |
7. Job preso / não processado
Diagnóstico:
php artisan horizon:status
php artisan queue:failed
php artisan queue:failed --queue=nome_da_fila
Solução:
php artisan queue:retry all
php artisan queue:failed-table
8. Vite/Frontend não atualiza
Sintoma: CSS ou JS não reflete mudanças.
Solução: Pedir ao usuário para executar npm run build ou npm run dev.
9. SQLSTATE[42000]: only_full_group_by — Filament TableWidget com GROUP BY
Causa: Filament TableWidget adiciona automaticamente ORDER BY tabela.id ASC como tiebreaker de paginação. Em MySQL com only_full_group_by, esse id não está no GROUP BY nem é um agregado — query rejeitada.
Sintoma típico no erro:
SQLSTATE[42000]: ... Expression #2 of ORDER BY clause is not in GROUP BY clause
... order by total_revenue desc, profitability_items.id asc
Diagnóstico: A query usa ->groupBy(...) + ->orderByDesc(...), e Filament appenda ORDER BY model.id.
Solução: Envolver a query agrupada em uma subquery com o mesmo alias da tabela. O Filament aplica seus sorts na query externa (sem GROUP BY), tornando-a compatível com only_full_group_by:
protected function getTableQuery(): Builder
{
$subQuery = MyModel::query()
->selectRaw('
grouped_col as id, // alias id = coluna agrupada
grouped_col,
SUM(value) as total,
...
')
->where(...)
->groupBy('grouped_col', 'item_name')
->orderByDesc('total')
->limit(10);
return MyModel::query()
->fromSub($subQuery, 'mymodels')
->orderByDesc('total');
}
Regras:
- O alias do
fromSub() deve ser exatamente o nome da tabela do model (ex: profitability_items)
- Manter o
->orderByDesc() na query externa para que o sort do Filament seja tiebreaker, não o sort primário
- O
->limit() deve ficar no subquery (garante os top N antes do Filament reordenar)
Busca Específica por Tipo de Erro
Erros de null-safe em produção
grep -n "Attempt to read property\|Call to a member function.*on null" storage/logs/laravel.log | tail -20
Queries lentas / N+1
grep -n "select \*\|N+1\|QueryException" storage/logs/laravel.log | tail -20
Erros de validação Filament
grep -n "ValidationException\|validation.required\|validation.unique" storage/logs/laravel.log | tail -20
Erros de autorização
grep -n "AuthorizationException\|403\|This action is unauthorized" storage/logs/laravel.log | tail -20
Relatório de Diagnóstico
Ao concluir, gerar:
## Diagnóstico: [Descrição do Erro]
### Evidências Coletadas
- Log: [linha relevante]
- Browser: [erro do console se houver]
- Banco: [estado dos dados]
### Causa Raiz
[Explicação técnica da causa]
### Correção Aplicada
- Arquivo: [path:linha]
- Mudança: [descrição]
### Verificação
- [ ] Testes executados: `php artisan test --compact --filter=...`
- [ ] Logs verificados após correção
- [ ] Pint executado: `vendor/bin/pint --dirty`