Fix ticket page 500 from unbounded audit timeline (#112)
Lint / PHP (phpcs PSR-12) (push) Successful in 21s
Lint / JS (eslint) (push) Successful in 8s
Lint / PHP requirements (version + extensions) (push) Successful in 20s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 1m22s
Lint / Deploy (push) Successful in 4s

A ticket updated by hwmonDaemon had ~52k audit rows; getTicketTimeline()
loaded all of them and exhausted PHP's 128MB memory limit, so the ticket
page returned 500. Not related to the MCP server.

- getTicketTimeline() takes a limit (newest first). The ticket page shows
  the latest 500 events with a note when older ones are omitted; the JSON
  export caps at 5000.
- create_ticket_api.php: the description is refreshed on every run, and
  that alone wrote a reason-only audit row every few minutes per open
  ticket. Audit only real title/priority changes.
- create_ticket_api.php: after creating a brand-new ticket the dedup retry
  loop fell through into a second iteration on a closed connection,
  appending a 500 error body after the success response. Exit instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
This commit is contained in:
2026-09-25 15:34:44 -04:00
co-authored by Claude Opus 5.5
parent dad066cb32
commit ad201691e4
5 changed files with 37 additions and 10 deletions
+10 -3
View File
@@ -561,20 +561,27 @@ class AuditLogModel
* Get formatted timeline for a specific ticket
* Includes all ticket updates and comments
*
* Returns only the newest $limit events: a ticket touched by an automated
* reporter can accumulate tens of thousands of rows, and loading them all
* exhausted PHP memory on the ticket page (tinker_tickets#112).
*
* @param string $ticketId Ticket ID
* @param int $limit Maximum number of events (newest first)
* @return array Timeline events
*/
public function getTicketTimeline($ticketId)
public function getTicketTimeline($ticketId, $limit = self::DEFAULT_LIMIT)
{
$limit = $this->validateLimit((int)$limit, self::EXPORT_LIMIT);
$stmt = $this->conn->prepare(
"SELECT al.*, u.username, u.display_name
FROM audit_log al
LEFT JOIN users u ON al.user_id = u.user_id
WHERE (al.entity_type = 'ticket' AND al.entity_id = ?)
OR (al.entity_type = 'comment' AND JSON_UNQUOTE(JSON_EXTRACT(al.details, '$.ticket_id')) = ?)
ORDER BY al.created_at DESC"
ORDER BY al.created_at DESC, al.audit_id DESC
LIMIT ?"
);
$stmt->bind_param("ss", $ticketId, $ticketId);
$stmt->bind_param("ssi", $ticketId, $ticketId, $limit);
$stmt->execute();
$result = $stmt->get_result();