From laraclaude
Finds orphaned database records where foreign key parent records don't exist. Uses Docker and Laravel to scan migrations or information_schema for foreign key relationships, with optional fix subcommand.
How this skill is triggered — by the user, by Claude, or both
Slash command
/laraclaude:orphaned-records [analyze | fix | fix --dry-run | table-name | fix table-name][analyze | fix | fix --dry-run | table-name | fix table-name]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Detect orphaned database records where a foreign key column references a parent record that no longer exists. This runs actual queries against the database via Docker.
Detect orphaned database records where a foreign key column references a parent record that no longer exists. This runs actual queries against the database via Docker.
| Subcommand | Description |
|---|---|
| (no argument) | Scan all tables for orphaned records. Read-only report. |
fix | Find and delete or nullify orphaned records. Asks for confirmation before each table. |
fix --dry-run | Show what cleanup queries would run without executing them. |
[table-name] | Analyze orphaned records in a specific table only. |
fix [table-name] | Fix orphaned records in a specific table, with confirmation. |
Read to examine docker-compose.yml in the project root.container_name or derive from service name).Bash:
docker ps --filter name={container_name} --format '{{.Names}}'
docker compose up -d.If a [table-name] argument is provided, only analyze that table. Otherwise, analyze all tables.
Method A: From Migrations (primary)
Glob to find all database/migrations/*.php files.Grep and Read to parse foreign key definitions:
$table->foreign('column')->references('ref_column')->on('ref_table')$table->foreignId('ref_table_id')->constrained()$table->foreignId('column')->constrained('ref_table')$table->foreignIdFor(Model::class){table}.{column} -> {ref_table}.{ref_column}Method B: From Database (supplementary) If migrations are complex or unclear, query the database directly:
docker exec {container} php artisan tinker --execute="
\$results = DB::select('
SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME IS NOT NULL
AND TABLE_SCHEMA = DB::getDatabaseName()
');
foreach (\$results as \$r) {
echo \$r->TABLE_NAME . '.' . \$r->COLUMN_NAME . ' -> ' . \$r->REFERENCED_TABLE_NAME . '.' . \$r->REFERENCED_COLUMN_NAME . PHP_EOL;
}
"
For each FK relationship, run a query to find records where the parent doesn't exist:
docker exec {container} php artisan tinker --execute="
\$count = DB::table('{table}')
->whereNotNull('{column}')
->whereNotIn('{column}', DB::table('{ref_table}')->select('{ref_column}'))
->count();
echo '{table}.{column} -> {ref_table}.{ref_column}: ' . \$count . ' orphans';
"
For tables with many records, use a LEFT JOIN approach for better performance:
SELECT COUNT(*) FROM {table} t
LEFT JOIN {ref_table} r ON t.{column} = r.{ref_column}
WHERE t.{column} IS NOT NULL AND r.{ref_column} IS NULL
For each relationship with orphaned records, fetch sample IDs and values:
docker exec {container} php artisan tinker --execute="
\$samples = DB::table('{table}')
->select('id', '{column}')
->whereNotNull('{column}')
->whereNotIn('{column}', DB::table('{ref_table}')->select('{ref_column}'))
->limit(5)
->get();
foreach (\$samples as \$s) {
echo 'ID: ' . \$s->id . ' | {column}: ' . \$s->{column} . PHP_EOL;
}
"
Before reporting orphans, check if the referenced table uses soft deletes:
Grep to check if the referenced model uses SoftDeletes trait.SELECT COUNT(*) FROM {table} t
LEFT JOIN {ref_table} r ON t.{column} = r.{ref_column}
WHERE t.{column} IS NOT NULL AND r.{ref_column} IS NULL
vs.
SELECT COUNT(*) FROM {table} t
INNER JOIN {ref_table} r ON t.{column} = r.{ref_column} AND r.deleted_at IS NOT NULL
WHERE t.{column} IS NOT NULL
Present findings in a structured format:
ORPHANED RECORDS REPORT
========================
Table: operations
-----------------
Column: client_seller_id -> clients.id
Orphaned records: 12
Soft-deleted parents: 3 (these have deleted parent records)
Truly orphaned: 9 (parent record does not exist at all)
Sample IDs: 45, 67, 89, 102, 115
FK onDelete action: SET NULL
Column: property_id -> properties.id
Orphaned records: 0 (clean)
Table: activities
-----------------
Column: activitable_id (polymorphic)
Note: Polymorphic relationships cannot be checked via FK constraints.
Skipped -- use manual inspection if needed.
fix or fix [table-name])For each table with orphaned records, offer the appropriate fix based on FK's onDelete action and column nullability:
For non-nullable FK columns or when records are meaningless without the parent:
DB::table('{table}')
->whereNotNull('{column}')
->whereNotIn('{column}', DB::table('{ref_table}')->select('{ref_column}'))
->delete();
For nullable FK columns where the record is still meaningful:
DB::table('{table}')
->whereNotNull('{column}')
->whereNotIn('{column}', DB::table('{ref_table}')->select('{ref_column}'))
->update(['{column}' => null]);
For production-safe approach, create a migration file:
public function up()
{
DB::statement('DELETE FROM {table} WHERE {column} NOT IN (SELECT {ref_column} FROM {ref_table}) AND {column} IS NOT NULL');
}
Each fix requires explicit user confirmation before executing. The user chooses which option (A, B, or C) for each table.
fix --dry-run)Show exactly what queries would be executed and how many records would be affected, without running any destructive queries. The count queries still run to show impact.
End with a summary:
SUMMARY
========
Tables checked: 35
FK relationships checked: 62
Tables with orphans: 4
Total orphaned records: 47
Soft-deleted parents: 8
Truly orphaned: 39
Polymorphic (skipped): 5
morphTo, morphMany) cannot be checked via FK constraints since they use type+id columns. These are flagged but skipped.npx claudepluginhub edulazaro/laraclaude --plugin laraclaudeDetect broken, orphaned, or missing foreign key constraints in Laravel migrations and models.
Reviews Rails models and migrations for data constraints, foreign keys, referential integrity, race conditions, and orphan risks. Provides checklists, queries, and fix recommendations.
Guides schema design, migration safety, and ORM analysis. Detects database engine and ORM, evaluates normalization and indexing, and validates backward compatibility.