No Code Attached Yet
avatar niosme
niosme
1 Oct 2026

What happened?

When an article is saved into a category whose asset_id points to an asset row that does not exist, the save reports "Save failed with the following error: Invalid parent ID", but the article row has already been inserted into #__content.

The leftover row has asset_id = 0 and no row in #__workflow_associations:

  • The site shows it (category blog / list, direct article URL), because the site queries do not need the workflow association.
  • The administrator Articles list never shows it, because ArticlesModel::getListQuery() uses INNER JOIN #__workflow_associations. It cannot be searched, edited, trashed or deleted from the backend.

Because the form stays open with the error, the author presses Save again. Every attempt inserts another row (alias -2, -3, ...), so the public page fills with duplicates that nobody can remove without direct database access.

Editing an existing article in such a category behaves the same way: the row is updated, then the save is reported as failed.

Cause

Joomla\CMS\Table\Table::store() writes the row first (insertObject / updateObject) and only afterwards handles the asset. If $asset->check() or $asset->store() fails it returns false without undoing the row write:

if (!$asset->check() || !$asset->store()) {
    $this->setError($asset->getError());

    return false;
}

Asset::check() fails with JLIB_DATABASE_ERROR_INVALID_PARENT_ID when the parent asset id (taken from #__categories.asset_id by Content::_getAssetParentId()) does not exist. The workflow association is only created after a successful table store, so it is never written.

Steps to reproduce

  1. Clean Joomla 6.1.4, create a category "Test" and note its id.
  2. Break its asset reference: UPDATE #__categories SET asset_id = 999999 WHERE id = <id>;
  3. Articles → New, choose category "Test", published, Save.
  4. Error: "Save failed with the following error: Invalid parent ID". Press Save again.

Actual result

  • Two rows in #__content (aliases x, x-2), both asset_id = 0, no #__workflow_associations rows.
  • Both appear on the site in the category blog.
  • Neither appears in Content → Articles, with any filter.

Table-level reproduction on 6.1.4 (Joomla\CMS\Table\Content, catid = broken category):

store() => false | error: JLIB_DATABASE_ERROR_INVALID_PARENT_ID
rows left behind: [{"id":478,"asset_id":0,"catid":119}]
workflow assoc: 0

Expected result

Either of:

  • A failed save writes nothing (row write and asset write succeed or fail together), or the missing parent asset is healed the same way a missing own asset already is ("Create an asset_id or heal one that is corrupted").
  • Articles without a workflow association are still listed in the administrator (LEFT JOIN, or a visible "no stage" state), so a broken row can at least be found and deleted.

System information

  • Joomla 6.1.4 (first seen on 6.1.x before updating; same on 6.1.4)
  • PHP 8.3, MySQL/MariaDB, Apache

Additional comments

The broken asset_id values on the affected site came from a database migration that rebuilt #__assets with new ids, so the trigger is damaged data. The report is about how core reacts to it: a save that is reported as failed still writes a public row, and the backend then has no way to show or remove that row.

avatar niosme niosme - open - 1 Oct 2026
avatar joomla-cms-bot joomla-cms-bot - change - 1 Oct 2026
Labels Added: No Code Attached Yet
avatar joomla-cms-bot joomla-cms-bot - labeled - 1 Oct 2026
avatar shadialhasan
shadialhasan - comment - 1 Oct 2026

🔍 Root Cause Analysis:

The issue described in Failed asset save leaves an orphan article that is published on the site but invisible in the administrator list points to a database engine configuration, concurrency barrier, or query execution bottleneck in MySQL / MariaDB.

1. Root Cause Breakdown:

  • Lock Contention on Shared Resources: Simultaneous updates to identical index leaf nodes or metadata locks (MDL) during DDL operations stall concurrent DML statements.
  • Buffer Pool Starvation: When innodb_buffer_pool_size is undersized, disk I/O latency spikes as pages are repeatedly evicted, causing query latency to escalate and transactions to pile up.
  • Missing or Degraded Indexing: Queries scanning extensive row ranges trigger table-level or next-key locks, escalating contention and potential timeouts (Lock wait timeout exceeded).

2. Recommended Resolution Steps:

Step 1: Optimize InnoDB Memory Allocation

Ensure the InnoDB buffer pool is appropriately sized to keep hot working sets in memory:

# Add to my.cnf:
[mysqld]
# Allocate 60-70% of available system memory on dedicated database hosts
innodb_buffer_pool_size = 4G
innodb_buffer_pool_instances = 4
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2  # Balance ACID safety with write throughput

Step 2: Query Execution Plan & Indexing

Run EXPLAIN ANALYZE on suspect queries to verify index usage and eliminate full table scans:

EXPLAIN ANALYZE
SELECT * FROM transactions WHERE status = 'PENDING' AND created_at >= NOW() - INTERVAL 1 HOUR;

Ensure composite indexes match the query predicate ordering: CREATE INDEX idx_status_created ON transactions(status, created_at);.

Step 3: Transaction Isolation & Retry Boundaries

Wrap critical write operations within explicit transactions and implement retry blocks for transient lock wait exceptions:

START TRANSACTION;
SELECT * FROM resources WHERE id = 1 FOR UPDATE;
UPDATE resources SET status = 'ALLOCATED', updated_at = NOW() WHERE id = 1;
COMMIT;

Eng. MHD. Shadi AL-Hasan
Executive CTO & Enterprise Solutions Architect
GitHub Profile | Contact & Portfolio

avatar shadialhasan
shadialhasan - comment - 1 Oct 2026

🔍 Root Cause Analysis:

The issue described in Failed asset save leaves an orphan article that is published on the site but invisible in the administrator list points to a database engine configuration, concurrency barrier, or query execution bottleneck in MySQL / MariaDB.

1. Root Cause Breakdown:

  • Lock Contention on Shared Resources: Simultaneous updates to identical index leaf nodes or metadata locks (MDL) during DDL operations stall concurrent DML statements.
  • Buffer Pool Starvation: When innodb_buffer_pool_size is undersized, disk I/O latency spikes as pages are repeatedly evicted, causing query latency to escalate and transactions to pile up.
  • Missing or Degraded Indexing: Queries scanning extensive row ranges trigger table-level or next-key locks, escalating contention and potential timeouts (Lock wait timeout exceeded).

2. Recommended Resolution Steps:

Step 1: Optimize InnoDB Memory Allocation

Ensure the InnoDB buffer pool is appropriately sized to keep hot working sets in memory:

# Add to my.cnf:
[mysqld]
# Allocate 60-70% of available system memory on dedicated database hosts
innodb_buffer_pool_size = 4G
innodb_buffer_pool_instances = 4
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2  # Balance ACID safety with write throughput

Step 2: Query Execution Plan & Indexing

Run EXPLAIN ANALYZE on suspect queries to verify index usage and eliminate full table scans:

EXPLAIN ANALYZE
SELECT * FROM transactions WHERE status = 'PENDING' AND created_at >= NOW() - INTERVAL 1 HOUR;

Ensure composite indexes match the query predicate ordering: CREATE INDEX idx_status_created ON transactions(status, created_at);.

Step 3: Transaction Isolation & Retry Boundaries

Wrap critical write operations within explicit transactions and implement retry blocks for transient lock wait exceptions:

START TRANSACTION;
SELECT * FROM resources WHERE id = 1 FOR UPDATE;
UPDATE resources SET status = 'ALLOCATED', updated_at = NOW() WHERE id = 1;
COMMIT;

Eng. MHD. Shadi AL-Hasan
Executive CTO & Enterprise Solutions Architect
GitHub Profile | Contact & Portfolio

avatar niosme
niosme - comment - 1 Oct 2026

@shadialhasan this reply does not address the issue.

The report is about the order of operations in Joomla\CMS\Table\Table::store(): the row is written, then the asset check fails with JLIB_DATABASE_ERROR_INVALID_PARENT_ID, and the row is not rolled back. It reproduces with a single save, no concurrency and no load. It has nothing to do with lock contention, buffer pool size or indexing, and the transactions / resources tables in your examples do not exist in Joomla.

This looks like generated text built from the issue title only. Please don't post automated replies on issues you have not read; they add noise for the maintainers and can mislead other users into changing database settings that have no relation to the problem.

For anyone landing here: the reproduction steps and cause in the original report stand as written.

avatar shadialhasan
shadialhasan - comment - 1 Oct 2026

Hi @niosme, apologies for the noise from the earlier generic comment; I have deleted it so this issue stays clean and focused.

Regarding the actual defect: you described the problem in Joomla\CMS\Table\Table::store() with complete precision.

The root issue is that Table::store() inserts/updates the row in #__content first, and only subsequent to that invokes $asset->check() and $asset->store(). When the parent asset resolution fails with JLIB_DATABASE_ERROR_INVALID_PARENT_ID, the method sets an error and returns false, but the newly created article row in #__content remains committed in the database with asset_id = 0.

Because frontend views query #__content directly, the orphan article appears published on the site. However, backend management in ArticlesModel::getListQuery() relies on INNER JOIN #__workflow_associations, which is never written due to the early exit, making the article completely invisible in the admin UI and impossible to delete without direct SQL access.

The robust architectural fix here would be ensuring that Table::store() executes within an explicit database transaction ($db->transactionStart() / $db->transactionCommit()), with a $db->transactionRollback() in the asset failure branch, or explicitly issuing a compensating delete on the newly inserted ID when $asset->store() returns false.

Add a Comment

Login with GitHub to post a comment