Alert and record a lost recurring-ticket occurrence on creation failure (#88)
RecurringTicketModel::claimForRun() deliberately advances next_run_at before TicketModel::createTicket() runs, to prevent duplicate-ticket floods if creation fails partway and the cron retries. The tradeoff: if createTicket() then fails, that specific occurrence is gone forever with no record anywhere an admin would normally look — the catch block only wrote a line to stdout/the cron log. Added recordMissedOccurrence(), called from both the "createTicket() returned success:false" branch and the exception catch, which writes an audit_log entry (entity_type='recurring_ticket', action_type='error') and fires a new NotificationHelper::sendSystemAlert() — a generic operational alert (unlike the ticket-specific notification methods, it has no associated ticket) sent to the shared MATRIX_NOTIFY_USERS list regardless of any per-event toggle, so a silently-skipped recurring ticket surfaces immediately instead of requiring someone to grep cron logs. Verified against real MariaDB and a real webhook-capturing server: calling the recorder writes the audit_log row with the failure reason and schedule details, and fires the Matrix alert with the same information. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lhz7pGMaoTfL5sdYS5XiKv
This commit is contained in:
@@ -29,6 +29,38 @@ function logMessage($message)
|
||||
echo "[" . date('Y-m-d H:i:s') . "] " . $message . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a recurring-ticket occurrence that was claimed (next_run_at already
|
||||
* advanced to the next future run) but then failed to actually produce a
|
||||
* ticket. That claim-then-fail ordering is deliberate — it stops a failing
|
||||
* creation from re-firing and flooding duplicates on every subsequent cron
|
||||
* tick — but means this specific occurrence has no other record anywhere an
|
||||
* admin would normally look: no audit_log entry (nothing was created), no
|
||||
* Matrix "ticket created" alert, no failure table. Without this, it's simply
|
||||
* gone, silently, forever.
|
||||
*/
|
||||
function recordMissedOccurrence($auditLog, $recurring, $reason)
|
||||
{
|
||||
$auditLog->log(
|
||||
$recurring['created_by'],
|
||||
'error',
|
||||
'recurring_ticket',
|
||||
(string)$recurring['recurring_id'],
|
||||
[
|
||||
'reason' => $reason,
|
||||
'title_template' => $recurring['title_template'],
|
||||
'schedule_type' => $recurring['schedule_type'],
|
||||
]
|
||||
);
|
||||
|
||||
NotificationHelper::sendSystemAlert(
|
||||
"Recurring ticket occurrence lost: schedule #{$recurring['recurring_id']} "
|
||||
. "(\"{$recurring['title_template']}\") was claimed for this run but ticket "
|
||||
. "creation failed, so this occurrence will not be created or retried.",
|
||||
['reason' => $reason, 'recurring_id' => $recurring['recurring_id']]
|
||||
);
|
||||
}
|
||||
|
||||
logMessage("Starting recurring tickets cron job");
|
||||
|
||||
try {
|
||||
@@ -100,11 +132,17 @@ try {
|
||||
|
||||
$created++;
|
||||
} else {
|
||||
logMessage("ERROR: Failed to create ticket - " . ($result['error'] ?? 'Unknown error'));
|
||||
$reason = $result['error'] ?? 'Unknown error';
|
||||
logMessage("ERROR: Failed to create ticket - " . $reason);
|
||||
recordMissedOccurrence($auditLog, $recurring, $reason);
|
||||
$errors++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
logMessage("ERROR: Exception processing recurring ticket - " . $e->getMessage());
|
||||
// claimForRun() already advanced next_run_at before this point, so
|
||||
// this occurrence is permanently gone unless recorded somewhere an
|
||||
// admin would actually look — a cron log line alone doesn't count.
|
||||
recordMissedOccurrence($auditLog, $recurring, $e->getMessage());
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user