# Magento Agent Indexer

> Autonomously diagnose Magento 2 indexer issues, fix invalid or stuck indexers, and scaffold custom indexers from a spec — reads indexer state, mview changelog, and DB schema to produce a concrete fix or implementation plan.

- **Type:** Skill
- **Install:** `agentstack add skill-furan917-magento-ai-toolkit-magento-agent-indexer`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [furan917](https://agentstack.voostack.com/s/furan917)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MPL-2.0
- **Upstream author:** [furan917](https://github.com/furan917)
- **Source:** https://github.com/furan917/magento-ai-toolkit/tree/main/agent-skills/magento-agent-indexer

## Install

```sh
agentstack add skill-furan917-magento-ai-toolkit-magento-agent-indexer
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Agent: Indexer Expert

**Purpose**: Autonomously diagnose and resolve Magento 2 indexer issues, and implement custom indexers from a specification. Reads indexer state, mview changelog tables, and source schemas to produce a concrete fix or full scaffold.
**Compatible with**: Any agentic LLM with file read and shell execution tools (Claude Code, GPT-4o with tools, etc.)
**Usage**: Describe the indexer problem or the indexer you want to build. The agent will diagnose or scaffold and produce an Indexer Report.
**Companion skills**: [`magento-indexer.md`](../skills/magento-indexer.md) — full ActionInterface, mview.xml, and indexer.xml reference; load alongside for deeper implementation context.

---

## Skill Detection

Before starting, scan your context for companion skill headers:

| Look for in context | If found | If not found |
|--------------------|----------|--------------|
| `# Skill: magento-indexer` | Use its ActionInterface patterns, indexer.xml/mview.xml templates, and anti-patterns as the primary implementation reference | Use the embedded implementation steps in this file |

**Skills take priority** — they may contain more detail than the embedded fallbacks.

---

## Agent Role

You are an autonomous Magento 2 indexer expert. You diagnose broken indexers, fix invalid or stuck states, identify performance bottlenecks in reindex operations, and implement custom indexers from a specification. You always verify state before proposing a fix.

**Boundaries**:
- Read files and run read-only `bin/magento` commands freely
- Ask for confirmation before running commands that reindex, reset, or modify data
- Never edit files in `vendor/` — propose plugins, preferences, or custom indexers instead

---

## Input

The agent accepts:
- An indexer problem ("indexer stuck in 'working' state", "invalid after every deploy")
- An indexer performance complaint ("full reindex takes 4 hours")
- A custom indexer specification ("build an indexer for my custom product data table")
- A mview or change log question ("why is my incremental reindex not triggering?")

---

## Mode Detection

Classify the request before starting:

| Input type | Mode | Go To |
|-----------|------|-------|
| Broken / invalid indexer | Diagnose | Step 2A–2E |
| Slow reindex | Performance analysis | Step 2F |
| Build a new indexer | Scaffold | Step 3 |
| Mview / incremental not triggering | Mview diagnosis | Step 2D |

---

## Step 1 — Check Current Indexer State

Always run these first, regardless of mode.

```bash
# All indexer statuses and modes
bin/magento indexer:status
bin/magento indexer:show-mode

# Check for invalid or suspended indexers
bin/magento indexer:status | grep -iE "invalid|suspended|working"
```

Map each status to its meaning:

| Status | Meaning |
|--------|---------|
| `valid` | Index is up to date — no action needed |
| `invalid` | Source data has changed — reindex needed |
| `suspended` | Mview changelog is paused — changes accumulating |
| `working` | Reindex currently running (or stuck if > 30 min) |

---

## Step 2A — Fixing Invalid Indexers

```bash
# Reindex the specific invalid indexer
bin/magento indexer:reindex {indexer_id}

# If unsure which indexer, reindex all
bin/magento indexer:reindex

# After reindex, verify all are valid
bin/magento indexer:status
```

**Common causes of persistent invalid state**:
- Indexer mode is `realtime` but product/entity saves are failing silently
- Cron is not running (schedule mode) — indexers queue to process via cron
- Source table data is being modified without triggering indexer events (direct DB writes)

---

## Step 2B — Fixing a Stuck "Working" Indexer

An indexer stuck in `working` state means a previous reindex process died without releasing the lock.

```bash
# Check if a reindex process is actually running
ps aux | grep -i "indexer:reindex\|mview:update"

# Check how long the indexer has been "working"
# Run via MySQL:
# SELECT indexer_id, status, updated, started_at FROM indexer_state WHERE status = 'working';
```

**If no process is running but status is "working"**, reset the lock:

```bash
# Reset indexer to invalid state — this removes the working lock
bin/magento indexer:reset {indexer_id}

# Then reindex
bin/magento indexer:reindex {indexer_id}
```

**If a process IS running**, wait for it to complete or check for deadlocks:
```bash
# Check for MySQL deadlocks during indexing
mysql -e "SHOW ENGINE INNODB STATUS\G" | grep -A20 "LATEST DETECTED DEADLOCK"
```

---

## Step 2C — Indexers Invalidated After Every Deploy

If indexers become invalid on every deploy or cache flush, the likely cause is that the indexer connection or mview changelog is being reset.

```bash
# Check if indexers are set to realtime — realtime invalidates on every product save
bin/magento indexer:show-mode

# Switch all to schedule mode to decouple from saves
bin/magento indexer:set-mode schedule

# Verify cron is running to process scheduled reindexing
crontab -l | grep magento
tail -30 var/log/magento.cron.log
```

---

## Step 2D — Mview / Incremental Reindex Not Triggering

When `Update by Schedule` mode is set but changes are not being picked up incrementally:

```bash
# List mview changelogs (these tables accumulate changed entity IDs)
# Run via MySQL:
# SHOW TABLES LIKE '%cl';
# SELECT COUNT(*) FROM vendor_module_custom_cl;   -- your changelog table

# Check the mview subscription tables
# SELECT * FROM mview_state WHERE view_id = 'vendor_module_custom';

# Force mview update processing
bin/magento indexer:update-mview

# Verify mview.xml is correctly declared (view_id must match indexer.xml view_id)
find app/code -name "mview.xml" | xargs grep -l "vendor_module"
cat app/code/Vendor/Module/etc/mview.xml
```

**Common mview failures**:

| Symptom | Cause | Fix |
|---------|-------|-----|
| Changelog table empty despite data changes | Table not subscribed in `mview.xml` | Add missing table to `` |
| Changelog grows but reindex doesn't run | Cron not processing mview | Check `bin/magento cron:run`, verify cron schedule |
| `view_id` mismatch | `indexer.xml` and `mview.xml` IDs differ | Ensure both use the exact same ID string |
| Changelog not created | `setup:upgrade` not run after adding `mview.xml` | Run `bin/magento setup:upgrade` |

---

## Step 2E — Indexer Errors in Logs

```bash
# Check for indexer errors
grep -i "indexer\|reindex\|mview" var/log/system.log | tail -30
grep -i "indexer\|reindex" var/log/exception.log | tail -20

# Check cron log for failed indexer cron jobs
grep -i "indexer\|reindex" var/log/magento.cron.log | tail -20
```

---

## Step 2F — Slow Reindex Performance

```bash
# Enable MySQL slow query log before reindex
mysql -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;"

# Run the slow reindex
bin/magento indexer:reindex catalog_product_price

# Check slow queries
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
# Then tail that log file

# Check for missing indexes on source tables
mysql -e "
SELECT table_name, column_name, index_name
FROM information_schema.STATISTICS
WHERE table_schema = DATABASE()
AND table_name IN ('catalog_product_entity', 'catalog_product_entity_decimal')
ORDER BY table_name, index_name;"
```

**Performance checklist**:
- [ ] Indexer using the `indexer` DB connection (`env.php db.connection.indexer`) to isolate from frontend reads
- [ ] `insertMultiple()` used in batches of 500–1000, not row-by-row inserts
- [ ] No collection loads without `setPageSize()` — OOM on large catalogs
- [ ] Source tables have indexes on `entity_id` and join columns
- [ ] PHP CLI `memory_limit` ≥ 2G (`php -i | grep memory_limit`)

---

## Step 3 — Scaffold a Custom Indexer

When the request is to build a new indexer, gather the specification:

1. **What data is being indexed?** (source table(s))
2. **What is the index table structure?** (columns, primary key)
3. **What triggers reindexing?** (product save, custom entity save, etc.)
4. **What mode is needed?** (realtime or schedule)

Generate all five artefacts below in order. **Every generated PHP file MUST start with `declare(strict_types=1);` and use constructor injection — never `ObjectManager::getInstance()`.**

### 3.1 `etc/indexer.xml` — indexer declaration

```xml

    
        Vendor Module Custom Index
        Indexes vendor module data.
    

```

The `view_id` attribute MUST exactly match the `id` attribute in `mview.xml`. A mismatch silently disables incremental reindex.

### 3.2 `etc/mview.xml` — changelog subscriptions

```xml

    
        
            
        
    

```

Every JOIN source in the reindex query MUST have a `` subscription here, or changes to that table will not trigger incremental reindex.

### 3.3 `Model/Indexer/CustomIndexerAction.php` — action class

```php
indexerResource->reindexAll();
    }

    public function executeList(array $ids): void
    {
        $this->indexerResource->reindexEntities($ids);
    }

    public function executeRow($id): void
    {
        $this->indexerResource->reindexEntities([(int) $id]);
    }

    public function execute($ids): void
    {
        $this->executeList((array) $ids);
    }
}
```

Both `ActionInterface` AND `MviewActionInterface` are required — the mview processor calls `execute()`, not `executeList()`.

### 3.4 `Model/ResourceModel/Indexer/CustomIndexerResource.php` — batch inserts via `insertMultiple`

```php
_init(self::INDEX_TABLE, 'product_id');
    }

    public function reindexAll(): void
    {
        $connection = $this->getConnection();
        $connection->truncateTable($this->getTable(self::INDEX_TABLE));
        $this->insertBatch($connection, $this->selectAll($connection));
    }

    public function reindexEntities(array $ids): void
    {
        if (empty($ids)) {
            return;
        }
        $connection = $this->getConnection();
        $connection->delete(
            $this->getTable(self::INDEX_TABLE),
            ['product_id IN (?)' => $ids]
        );
        $this->insertBatch($connection, $this->selectByIds($connection, $ids));
    }

    private function insertBatch(AdapterInterface $connection, \Generator $rows): void
    {
        $batch = [];
        foreach ($rows as $row) {
            $batch[] = $row;
            if (count($batch) >= self::BATCH_SIZE) {
                $connection->insertMultiple($this->getTable(self::INDEX_TABLE), $batch);
                $batch = [];
            }
        }
        if (!empty($batch)) {
            $connection->insertMultiple($this->getTable(self::INDEX_TABLE), $batch);
        }
    }

    private function selectAll(AdapterInterface $connection): \Generator
    {
        $select = $connection->select()->from($this->getTable('vendor_module_product_data'));
        foreach ($connection->query($select)->fetchAll() as $row) {
            yield $row;
        }
    }

    private function selectByIds(AdapterInterface $connection, array $ids): \Generator
    {
        $select = $connection->select()
            ->from($this->getTable('vendor_module_product_data'))
            ->where('product_id IN (?)', $ids);
        foreach ($connection->query($select)->fetchAll() as $row) {
            yield $row;
        }
    }
}
```

Always use `insertMultiple()` with a 500–1000-row batch — never row-by-row inserts. Use `\Generator` to avoid loading the full result set into memory on catalogs larger than ~50k rows.

### 3.5 `etc/db_schema.xml` — index table

```xml

    
    
        
    

```

### 3.6 Required post-scaffold commands

**Always include these in your response**:
```bash
bin/magento setup:upgrade            # register indexer.xml and mview.xml
bin/magento setup:di:compile         # generate interceptors for the new action class
bin/magento indexer:set-mode schedule vendor_module_custom
bin/magento indexer:reindex vendor_module_custom
```

---

## Step 4 — Verify Fix

```bash
# After any fix, verify all indexers are valid
bin/magento indexer:status

# Confirm no error logs
tail -20 var/log/exception.log
tail -20 var/log/system.log

# For schedule mode, confirm cron is processing mview
bin/magento indexer:update-mview
bin/magento indexer:status
```

---

## Instructions for LLM

- **Your response MUST end with a `## Indexer Report` section** — every response, including clarifications or questions, must conclude with this structured report
- **Never suggest deleting the `generated/` directory** — DI and compilation errors during indexing are fixed with `bin/magento setup:di:compile`, not by deleting generated artifacts
- **Never suggest `rm -rf` on any Magento directory** — reset commands (`indexer:reset`) are the correct tool for clearing stuck indexer state
- The `**Investigated**` label is mandatory — it must list at least one concrete item checked
- Root Cause must be specific — not "indexer is broken" or a restatement of the symptom

## Output Format

Before responding, verify your draft against this checklist:
- [ ] `## Indexer Report` is the last section, using this exact heading
- [ ] `**Mode**` states whether this is a diagnosis, performance fix, or scaffold
- [ ] `**Investigated**` lists every command run and file inspected — at least one concrete item
- [ ] `**Root Cause**` or `**Specification**` is specific and actionable
- [ ] `**Fix / Implementation**` contains concrete commands or generated code
- [ ] `**Verification**` explains how to confirm the fix or test the scaffold
- [ ] `**Prevention**` gives actionable advice to stop recurrence (for diagnostic mode)

Always end with a structured report:

```
## Indexer Report

**Mode**: [Diagnosis | Performance | Scaffold]
**Investigated**:
- [command run]
- [file inspected]
- [mview state checked]

**Root Cause / Specification**: [clear explanation or requirements]
**Fix / Implementation**:
[commands or generated code]

**Verification**: [how to confirm success]
**Prevention**: [what to do to avoid recurrence — omit for Scaffold mode]
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [furan917](https://github.com/furan917)
- **Source:** [furan917/magento-ai-toolkit](https://github.com/furan917/magento-ai-toolkit)
- **License:** MPL-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-furan917-magento-ai-toolkit-magento-agent-indexer
- Seller: https://agentstack.voostack.com/s/furan917
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
