Skip to content

[SI-610]: Add task queue maintenance script - #190

Open
tatsianazh wants to merge 12 commits into
developfrom
feat/SI-610/task-queue-maintenance
Open

[SI-610]: Add task queue maintenance script#190
tatsianazh wants to merge 12 commits into
developfrom
feat/SI-610/task-queue-maintenance

Conversation

@tatsianazh

@tatsianazh tatsianazh commented Dec 5, 2025

Copy link
Copy Markdown

https://oat-sa.atlassian.net/browse/SI-610

  • What it does:

Adds TaskQueueMaintenance script that is supposed to be run by a cron-job.
--archive: archives completed/failed tasks older than period of time specified number of days
--unblock: unblocks stuck tasks (set visible=='t') older than specified number of days
--vacuum: runs VACUUM FULL tq_task_log.
--delete: deletes archived tasks older than period of time specified number of days

How to run:

php index.php 'oat\taoTaskQueue\scripts\tools\TaskQueueMaintenance' --archive
php index.php 'oat\taoTaskQueue\scripts\tools\TaskQueueMaintenance' --unblock
php index.php 'oat\taoTaskQueue\scripts\tools\TaskQueueMaintenance' --vacuum
php index.php 'oat\taoTaskQueue\scripts\tools\TaskQueueMaintenance' --delete

  • Optional parameters can be added to override the default retention period in days:

php index.php 'oat\taoTaskQueue\scripts\tools\TaskQueueMaintenance' --archive -cr [n-days]
php index.php 'oat\taoTaskQueue\scripts\tools\TaskQueueMaintenance' --unblock -sr [n-days]
php index.php 'oat\taoTaskQueue\scripts\tools\TaskQueueMaintenance' --delete -ar [n-days]

Summary by CodeRabbit

Release Notes

  • New Features
    • New maintenance script enables automated task queue cleanup with configurable retention windows for completed, archived, and stuck tasks
    • Supports archive, delete, unblock, and database optimization operations

@tatsianazh
tatsianazh requested a review from siwane December 5, 2025 16:40
@coderabbitai

coderabbitai Bot commented Dec 5, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Introduces a new maintenance script extending ScriptAction that provides cron-oriented operations for task queue management. Supports archive, delete, unblock, and vacuum operations with configurable retention periods for completed tasks (30 days), archived tasks (180 days), and stuck tasks (2 days).

Changes

Cohort / File(s) Summary
Task Queue Maintenance Script
scripts/tools/TaskQueueMaintenance.php
New maintenance script implementing four operations: archive (moves old COMPLETED/FAILED tasks), delete (removes old ARCHIVED tasks), unblock (restores visibility to stuck tasks in RUNNING/ENQUEUED states), and vacuum (executes PostgreSQL VACUUM on task log table). Includes configurable retention windows and service locator integration.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Script as TaskQueueMaintenance
    participant TaskLog as TaskLogInterface
    participant Broker as Task Log Broker
    
    User->>Script: Execute archive operation
    Script->>TaskLog: Query COMPLETED/FAILED tasks<br/>older than 30 days
    TaskLog-->>Script: Return matched tasks
    alt No tasks found
        Script-->>User: Return success report
    else Tasks found
        Script->>Broker: Archive collection
        Broker-->>Script: Confirmation
        Script-->>User: Return success with count
    end
Loading
sequenceDiagram
    actor User
    participant Script as TaskQueueMaintenance
    participant TaskLog as TaskLogInterface
    participant QueueDisp as QueueDispatcherInterface
    participant RdsBroker as RdsQueueBroker
    participant DB as Persistence/DB
    
    User->>Script: Execute unblock operation
    Script->>TaskLog: Query RUNNING/ENQUEUED tasks<br/>with updated_at ≤ cutoff
    TaskLog-->>Script: Return matched tasks
    alt Empty collection
        Script-->>User: Return success report
    else Tasks found
        loop For each task log entry
            Script->>QueueDisp: Get available queues
            QueueDisp-->>Script: Return queue list
            loop For each queue
                Script->>RdsBroker: Get task by task log ID
                alt Task found in queue
                    RdsBroker->>DB: Query visibility from tq_queue table
                    DB-->>RdsBroker: Return visible status
                    alt Not visible
                        Script->>DB: Set visibility to true
                        DB-->>Script: Confirmation
                        Script->>Script: Count as unblocked
                    else Already visible
                        Script->>Script: Count as already visible
                    end
                else Task not found
                    Script->>Script: Count as orphaned
                end
            end
        end
        Script-->>User: Return aggregated stats report
    end
Loading
sequenceDiagram
    actor User
    participant Script as TaskQueueMaintenance
    participant Driver as Persistence Driver
    participant PDO as PDO Connection
    
    User->>Script: Execute vacuum operation
    Script->>Driver: Verify driver is SQL with PostgreSQL
    alt Not compatible
        Script-->>User: Return error report
    else Compatible
        Script->>PDO: Execute VACUUM FULL tq_task_log
        PDO-->>Script: Confirmation
        Script-->>User: Return success report
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

⏱️ 10-30 Min Review

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'SI-610: Add task queue maintenance script' clearly and specifically describes the main change: the addition of a new maintenance script for task queue operations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/SI-610/task-queue-maintenance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread scripts/tools/TaskQueueMaintenance.php
Comment thread scripts/tools/TaskQueueMaintenance.php Outdated
Comment thread scripts/tools/TaskQueueMaintenance.php
Comment thread scripts/tools/TaskQueueMaintenance.php Outdated
Comment thread scripts/tools/TaskQueueMaintenance.php Outdated
Comment thread scripts/tools/TaskQueueMaintenance.php
Comment thread scripts/tools/TaskQueueMaintenance.php Outdated
Comment thread scripts/tools/TaskQueueMaintenance.php Outdated
@tatsianazh
tatsianazh marked this pull request as ready for review December 5, 2025 16:59
@tatsianazh
tatsianazh marked this pull request as draft December 5, 2025 17:01
@github-actions

github-actions Bot commented Dec 23, 2025

Copy link
Copy Markdown

Front-end summary Node 18

💯 Total ✅ Passed ⏭️ Skipped ❌ Failed
115 115 0 0

@tatsianazh tatsianazh self-assigned this Jan 5, 2026
@tatsianazh
tatsianazh marked this pull request as ready for review January 7, 2026 13:45
Comment thread scripts/tools/TaskQueueMaintenance.php Outdated
Comment thread scripts/tools/TaskQueueMaintenance.php Outdated
Comment thread scripts/tools/TaskQueueMaintenance.php Outdated
@tatsianazh
tatsianazh marked this pull request as draft February 6, 2026 09:24
@tatsianazh
tatsianazh marked this pull request as ready for review March 27, 2026 17:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/tools/TaskQueueMaintenance.php`:
- Around line 40-373: The PR adds TaskQueueMaintenance with substantial logic
but no tests; add unit tests that exercise archiveCompletedAndFailed,
deleteOldArchived, unblockStuckTasks, and runVacuum (positive flows) and
negative/failure paths (empty collections, broker delete failures, persistence
query returning no row, decorator null, changeTaskVisibility failures, and
non-pg driver for VACUUM). Write tests that instantiate TaskQueueMaintenance and
mock/getServiceLocator to return mocked TaskLogInterface (search,
archiveCollection, getBroker), TaskLogBrokerInterface (deleteById),
QueueDispatcherInterface (getQueues), Queue and RdsQueueBroker (getBroker,
getTaskByTaskLogId, changeTaskVisibility), and
common_persistence_Manager/common_persistence_SqlPersistence (query/exec/driver)
to simulate all branches and assert Report messages and counts; include tests
for retention parameter handling and error reports from runVacuum when driver is
not common_persistence_sql_Driver or driver param driver != 'pdo_pgsql'. Ensure
each test verifies both Report success/error contents and that mocked methods
(archiveCollection, deleteById, changeTaskVisibility, exec) are called the
expected number of times.
- Around line 369-371: Wrap the call to $persistence->exec('VACUUM FULL
tq_task_log;') in a try/catch so database failures do not abort the cron: try
executing the VACUUM, and on success return
Report::createSuccess('[TaskQueueMaintenance] Vacuum flow finished (VACUUM FULL
tq_task_log).'); catch the DB exception (e.g. PDOException or generic Exception)
and return Report::createError(...) containing a clear message and the exception
message/details so the cron returns an error report instead of crashing.
- Around line 125-127: After casting the options to ints ($completedRetention,
$archivedRetention, $stuckRetention) validate that none are negative; if any
value is < 0, short-circuit before computing cutoff dates by returning/raising a
clear error/report (use the existing task tool error reporting mechanism—e.g.
the class' error/report method or return a non‑zero status) with a message
naming the offending option(s) and expected >= 0 constraint; ensure this
validation sits immediately after the getOption casts so invalid inputs cannot
produce bad date modifiers.
- Around line 311-315: The code concatenates $queue->getName() into $tableName
and injects it into SQL for persistence->query, which risks SQL injection
because identifiers cannot be parameterized; instead validate or map the queue
name to a safe identifier before building the SQL: enforce a strict whitelist or
regex (e.g. only lowercase letters, digits and underscores) for
$queue->getName() (or use a known mapping of queue names to table names), then
construct $tableName as 'tq_' . $validatedName (or lookup) and only after that
use persistence->query with parameterized values (keeping $queueRowId as a
parameter); update the code around $tableName, $queue->getName(), and
persistence->query to implement this validation/mapping and throw/log on invalid
queue names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: https://raw.githubusercontent.com/oat-sa/tao-code-quality/main/coderabbit/php/authoring/v1/.coderabbit.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: c5913621-0fc4-47ba-828f-b3133d907c20

📥 Commits

Reviewing files that changed from the base of the PR and between 50e31df and c785b78.

📒 Files selected for processing (1)
  • scripts/tools/TaskQueueMaintenance.php

Comment on lines +40 to +373
class TaskQueueMaintenance extends ScriptAction implements ServiceLocatorAwareInterface
{
use ServiceLocatorAwareTrait;

protected function provideDescription(): string
{
return 'Cron-oriented maintenance script for task queue.';
}

protected function provideUsage(): array
{
return [
'prefix' => 'h',
'longPrefix' => 'help',
'description' => 'Display this help message.'
];
}

protected function provideOptions(): array
{
return [
'archive' => [
'prefix' => 'a',
'longPrefix' => 'archive',
'flag' => true,
'required' => false,
'description' => 'Archive completed/failed tasks older than retention.'
],
'delete' => [
'prefix' => 'd',
'longPrefix' => 'delete',
'flag' => true,
'required' => false,
'description' => 'Delete archived tasks older than retention.'
],
'vacuum' => [
'prefix' => 'v',
'longPrefix' => 'vacuum',
'flag' => true,
'required' => false,
'description' => 'Run VACUUM FULL on tq_task_log.'
],
'unblock' => [
'prefix' => 'u',
'longPrefix' => 'unblock',
'flag' => true,
'required' => false,
'description' => 'Unblock stuck tasks older than retention.'
],
'completedRetention' => [
'prefix' => 'cr',
'longPrefix' => 'completed-retention',
'cast' => 'int',
'required' => false,
'defaultValue' => 30,
'description' => 'Retention in days for completed/failed tasks before archiving.',
],
'archivedRetention' => [
'prefix' => 'ar',
'longPrefix' => 'archived-retention',
'cast' => 'int',
'required' => false,
'defaultValue' => 180,
'description' => 'Retention in days for archived tasks before deletion.',
],
'stuckRetention' => [
'prefix' => 'sr',
'longPrefix' => 'stuck-retention',
'cast' => 'int',
'required' => false,
'defaultValue' => 2,
'description' => 'Age in days for running/enqueued tasks to be considered stuck.',
],
];
}

/**
* Entry point when called via:
* sudo -u www-data php index.php 'oat\taoTaskQueue\scripts\tools\TaskQueueMaintenance'
*/
protected function run()
{
$report = Report::createInfo('[TaskQueueMaintenance] Maintenance run summary.');
$hasAction = false;

$completedRetention = (int) $this->getOption('completedRetention');
$archivedRetention = (int) $this->getOption('archivedRetention');
$stuckRetention = (int) $this->getOption('stuckRetention');

if ($this->getOption('archive')) {
$hasAction = true;
$report->add($this->archiveCompletedAndFailed($completedRetention));
}

if ($this->getOption('delete')) {
$hasAction = true;
$report->add($this->deleteOldArchived($archivedRetention));
}

if ($this->getOption('unblock')) {
$hasAction = true;
$report->add($this->unblockStuckTasks($stuckRetention));
}

if ($this->getOption('vacuum')) {
$hasAction = true;
$report->add($this->runVacuum());
}

if (!$hasAction) {
return Report::createInfo('No option provided. Use --help to see usage.');
}

return $report;
}

/**
* Move Completed and Failed tasks older than N days to Archived status in the tq_task_log table.
*/
private function archiveCompletedAndFailed(int $completedRetention): Report
{
/** @var TaskLogInterface $taskLog */
$taskLog = $this->getServiceLocator()->get(TaskLogInterface::SERVICE_ID);

$cutoffDate = (new DateTimeImmutable())
->modify(sprintf('-%d days', $completedRetention));

$cutoffDateString = $cutoffDate->format('Y-m-d H:i:s');

$filter = new TaskLogFilter();

$filter
->in(
TaskLogBrokerInterface::COLUMN_STATUS,
[
TaskLogInterface::STATUS_COMPLETED,
TaskLogInterface::STATUS_FAILED,
]
)
->lt(
TaskLogBrokerInterface::COLUMN_UPDATED_AT,
$cutoffDateString
);

$collection = $taskLog->search($filter);

if ($collection->isEmpty()) {
return Report::createSuccess('[TaskQueueMaintenance] Archive: nothing to archive.');
}

$taskLog->archiveCollection($collection);

return Report::createSuccess(sprintf(
'[TaskQueueMaintenance] Archive flow finished. Affected tasks: %d',
$collection->count()
));
}

/**
* Delete archived tasks older than N days.
*/
private function deleteOldArchived(int $archivedRetention): Report
{
/** @var TaskLogInterface $taskLog */
$taskLog = $this->getServiceLocator()->get(TaskLogInterface::SERVICE_ID);

$cutoffDate = (new DateTimeImmutable())
->modify(sprintf('-%d days', $archivedRetention));

$cutoffDateString = $cutoffDate->format('Y-m-d H:i:s');

$filter = new TaskLogFilter();

$filter
->eq(
TaskLogBrokerInterface::COLUMN_STATUS,
TaskLogInterface::STATUS_ARCHIVED
)
->lt(
TaskLogBrokerInterface::COLUMN_UPDATED_AT,
$cutoffDateString
);

$collection = $taskLog->search($filter);

if ($collection->isEmpty()) {
return Report::createSuccess('[TaskQueueMaintenance] Delete: nothing to delete.');
}

$deleted = 0;

$broker = $taskLog->getBroker();

foreach ($collection as $entity) {
/** @var \oat\tao\model\taskQueue\TaskLog\Entity\TaskLogEntityInterface|\ArrayAccess $entity */
if ($broker->deleteById($entity->getId())) {
$deleted++;
}
}

return Report::createSuccess(sprintf(
'[TaskQueueMaintenance] Delete archived flow finished. Tasks deleted: %d',
$deleted
));
}

/**
* Unblock stuck tasks in Running/Enqueued status older than $stuckRetention.
*/
private function unblockStuckTasks(int $stuckRetention): Report
{
/** @var TaskLogInterface $taskLog */
$taskLog = $this->getServiceLocator()->get(TaskLogInterface::SERVICE_ID);

/** @var QueueDispatcherInterface $dispatcher */
$dispatcher = $this->getServiceLocator()->get(QueueDispatcherInterface::SERVICE_ID);

/** @var \common_persistence_SqlPersistence $persistence */
$persistence = $this->getServiceLocator()
->get(common_persistence_Manager::SERVICE_ID)
->getPersistenceById('default');

$cutoff = (new DateTimeImmutable())
->modify(sprintf('-%d days', $stuckRetention))
->format('Y-m-d H:i:s');

$filter = (new TaskLogFilter())
->in(
TaskLogBrokerInterface::COLUMN_STATUS,
[
TaskLogInterface::STATUS_RUNNING,
TaskLogInterface::STATUS_ENQUEUED,
]
)
->lte(
TaskLogBrokerInterface::COLUMN_UPDATED_AT,
$cutoff
);

$collection = $taskLog->search($filter);

$stats = [
'stuckFound' => $collection->count(),
'unblocked' => 0,
'alreadyVisible' => 0,
'orphan' => 0,
];

if ($collection->isEmpty()) {
return Report::createSuccess('[TaskQueueMaintenance] Unblock: nothing to unblock.');
}

/** @var \oat\tao\model\taskQueue\Queue[] $queues */
$queues = $dispatcher->getQueues();

foreach ($collection as $taskLogEntity) {
$taskLogId = $taskLogEntity->getId();
$foundInQueue = false;

foreach ($queues as $queue) {
$broker = $queue->getBroker();

/** @var RdsQueueBroker $broker */
$decorator = $broker->getTaskByTaskLogId($taskLogId);
if ($decorator === null) {
continue;
}

$foundInQueue = true;

$queueRowId = (int) $decorator->getTaskId();
$tableName = 'tq_' . $queue->getName();

$row = $persistence->query(
'SELECT visible FROM ' . $tableName . ' WHERE id = ?',
[$queueRowId]
)->fetch(PDO::FETCH_ASSOC);

if (!$row) {
continue;
}

if ((bool) $row['visible'] === false) {
$broker->changeTaskVisibility((string) $queueRowId, true);
$stats['unblocked']++;
} else {
$stats['alreadyVisible']++;
}

break;
}

if (!$foundInQueue) {
$stats['orphan']++;
}
}

return Report::createSuccess(sprintf(
'[TaskQueueMaintenance] Unblock finished. found=%d already_visible=%d unblocked=%d orphan=%d',
$stats['stuckFound'],
$stats['alreadyVisible'],
$stats['unblocked'],
$stats['orphan']
));
}

/**
* Run VACUUM FULL on the tq_task_log table.
*/
private function runVacuum(): Report
{
/** @var common_persistence_Manager $pm */
$pm = $this->getServiceLocator()->get(common_persistence_Manager::SERVICE_ID);
$persistence = $pm->getPersistenceById('default');

/** @var common_persistence_Driver $driver */
$driver = $persistence->getDriver();

if (!($driver instanceof common_persistence_sql_Driver)) {
return Report::createError('VACUUM FULL is only supported on PostgreSQL (pdo_pgsql)');
}

$conn = $driver->getDbalConnection()->getParams();
$driverType = $conn['driver'] ?? null;

if ($driverType !== 'pdo_pgsql') {
return Report::createError('VACUUM FULL is only supported on PostgreSQL (pdo_pgsql)');
}

$persistence->exec('VACUUM FULL tq_task_log;');

return Report::createSuccess('[TaskQueueMaintenance] Vacuum flow finished (VACUUM FULL tq_task_log).');
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add unit tests for all maintenance flows and failure paths.

This new script introduces substantial logic but no corresponding tests are included in this PR context (archive/delete/unblock/vacuum, including negative cases and broker/persistence failures).

As per coding guidelines, "All new code MUST include appropriate unit tests" and "Tests MUST cover both positive and negative scenarios."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/tools/TaskQueueMaintenance.php` around lines 40 - 373, The PR adds
TaskQueueMaintenance with substantial logic but no tests; add unit tests that
exercise archiveCompletedAndFailed, deleteOldArchived, unblockStuckTasks, and
runVacuum (positive flows) and negative/failure paths (empty collections, broker
delete failures, persistence query returning no row, decorator null,
changeTaskVisibility failures, and non-pg driver for VACUUM). Write tests that
instantiate TaskQueueMaintenance and mock/getServiceLocator to return mocked
TaskLogInterface (search, archiveCollection, getBroker), TaskLogBrokerInterface
(deleteById), QueueDispatcherInterface (getQueues), Queue and RdsQueueBroker
(getBroker, getTaskByTaskLogId, changeTaskVisibility), and
common_persistence_Manager/common_persistence_SqlPersistence (query/exec/driver)
to simulate all branches and assert Report messages and counts; include tests
for retention parameter handling and error reports from runVacuum when driver is
not common_persistence_sql_Driver or driver param driver != 'pdo_pgsql'. Ensure
each test verifies both Report success/error contents and that mocked methods
(archiveCollection, deleteById, changeTaskVisibility, exec) are called the
expected number of times.

Comment on lines +125 to +127
$completedRetention = (int) $this->getOption('completedRetention');
$archivedRetention = (int) $this->getOption('archivedRetention');
$stuckRetention = (int) $this->getOption('stuckRetention');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate retention inputs before cutoff-date computation.

Negative retention values are currently accepted and can produce invalid or unintended date modifiers. Reject < 0 values early and return an error report.

Proposed fix
         $completedRetention = (int) $this->getOption('completedRetention');
         $archivedRetention  = (int) $this->getOption('archivedRetention');
         $stuckRetention     = (int) $this->getOption('stuckRetention');
+
+        foreach (
+            [
+                'completedRetention' => $completedRetention,
+                'archivedRetention' => $archivedRetention,
+                'stuckRetention' => $stuckRetention,
+            ] as $name => $value
+        ) {
+            if ($value < 0) {
+                return Report::createError(sprintf('%s must be >= 0 days.', $name));
+            }
+        }
As per coding guidelines, "Implement proper input validation and sanitization."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$completedRetention = (int) $this->getOption('completedRetention');
$archivedRetention = (int) $this->getOption('archivedRetention');
$stuckRetention = (int) $this->getOption('stuckRetention');
$completedRetention = (int) $this->getOption('completedRetention');
$archivedRetention = (int) $this->getOption('archivedRetention');
$stuckRetention = (int) $this->getOption('stuckRetention');
foreach (
[
'completedRetention' => $completedRetention,
'archivedRetention' => $archivedRetention,
'stuckRetention' => $stuckRetention,
] as $name => $value
) {
if ($value < 0) {
return Report::createError(sprintf('%s must be >= 0 days.', $name));
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/tools/TaskQueueMaintenance.php` around lines 125 - 127, After casting
the options to ints ($completedRetention, $archivedRetention, $stuckRetention)
validate that none are negative; if any value is < 0, short-circuit before
computing cutoff dates by returning/raising a clear error/report (use the
existing task tool error reporting mechanism—e.g. the class' error/report method
or return a non‑zero status) with a message naming the offending option(s) and
expected >= 0 constraint; ensure this validation sits immediately after the
getOption casts so invalid inputs cannot produce bad date modifiers.

Comment on lines +311 to +315
$tableName = 'tq_' . $queue->getName();

$row = $persistence->query(
'SELECT visible FROM ' . $tableName . ' WHERE id = ?',
[$queueRowId]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid raw SQL identifier interpolation from queue names.

$tableName is built from $queue->getName() and concatenated into SQL. Even for internal values, this should be strictly validated before query construction.

Proposed fix
                 $queueRowId = (int) $decorator->getTaskId();
-                $tableName = 'tq_' . $queue->getName();
+                $queueName = $queue->getName();
+                if (!preg_match('/^[A-Za-z0-9_]+$/', $queueName)) {
+                    return Report::createError(
+                        sprintf('[TaskQueueMaintenance] Unsupported queue name: %s', $queueName)
+                    );
+                }
+                $tableName = 'tq_' . $queueName;
 
                 $row = $persistence->query(
-                    'SELECT visible FROM ' . $tableName . ' WHERE id = ?',
+                    sprintf('SELECT visible FROM %s WHERE id = ?', $tableName),
                     [$queueRowId]
                 )->fetch(PDO::FETCH_ASSOC);
As per coding guidelines, "Use parameterized queries to prevent SQL injection."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/tools/TaskQueueMaintenance.php` around lines 311 - 315, The code
concatenates $queue->getName() into $tableName and injects it into SQL for
persistence->query, which risks SQL injection because identifiers cannot be
parameterized; instead validate or map the queue name to a safe identifier
before building the SQL: enforce a strict whitelist or regex (e.g. only
lowercase letters, digits and underscores) for $queue->getName() (or use a known
mapping of queue names to table names), then construct $tableName as 'tq_' .
$validatedName (or lookup) and only after that use persistence->query with
parameterized values (keeping $queueRowId as a parameter); update the code
around $tableName, $queue->getName(), and persistence->query to implement this
validation/mapping and throw/log on invalid queue names.

Comment on lines +369 to +371
$persistence->exec('VACUUM FULL tq_task_log;');

return Report::createSuccess('[TaskQueueMaintenance] Vacuum flow finished (VACUUM FULL tq_task_log).');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle DB failures in vacuum execution.

$persistence->exec() is not wrapped in exception handling. A failure here can terminate the whole cron run instead of returning an error report.

Proposed fix
-        $persistence->exec('VACUUM FULL tq_task_log;');
+        try {
+            $persistence->exec('VACUUM FULL tq_task_log;');
+        } catch (\Throwable $exception) {
+            return Report::createError(
+                sprintf('[TaskQueueMaintenance] Vacuum failed: %s', $exception->getMessage())
+            );
+        }
As per coding guidelines, "Use structured exception handling with appropriate exception types."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$persistence->exec('VACUUM FULL tq_task_log;');
return Report::createSuccess('[TaskQueueMaintenance] Vacuum flow finished (VACUUM FULL tq_task_log).');
try {
$persistence->exec('VACUUM FULL tq_task_log;');
} catch (\Throwable $exception) {
return Report::createError(
sprintf('[TaskQueueMaintenance] Vacuum failed: %s', $exception->getMessage())
);
}
return Report::createSuccess('[TaskQueueMaintenance] Vacuum flow finished (VACUUM FULL tq_task_log).');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/tools/TaskQueueMaintenance.php` around lines 369 - 371, Wrap the call
to $persistence->exec('VACUUM FULL tq_task_log;') in a try/catch so database
failures do not abort the cron: try executing the VACUUM, and on success return
Report::createSuccess('[TaskQueueMaintenance] Vacuum flow finished (VACUUM FULL
tq_task_log).'); catch the DB exception (e.g. PDOException or generic Exception)
and return Report::createError(...) containing a clear message and the exception
message/details so the cron returns an error report instead of crashing.

@github-actions

Copy link
Copy Markdown

Version

Target Version 6.11.0
Last version 6.10.0

There are 0 BREAKING CHANGE, 4 features, 6 fixes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants