Bulk-action buttons have no double-submit guard #36

Closed
opened 2026-08-31 21:29:43 -04:00 by jared · 1 comment
Owner

Severity: Medium

assets/js/dashboard.js (~lines 142-165): all 4 bulk-action buttons (close/assign/priority/status) call their performBulk*() handler directly on click with no button-disable/in-flight guard.

Repro: Open the bulk-close modal, double-click "CLOSE TICKETS" quickly → two concurrent POST /api/bulk_operation.php calls fire for the same ticket IDs, each posting the close-reason comment per ticket — duplicate comments/audit-log entries on every affected ticket (and duplicate Matrix notifications), even though the tickets only actually close once.

Fix: Disable the confirm button (and/or set an in-flight flag) immediately on click, re-enabling only on request completion/error.

**Severity:** Medium `assets/js/dashboard.js` (~lines 142-165): all 4 bulk-action buttons (close/assign/priority/status) call their `performBulk*()` handler directly on click with no button-disable/in-flight guard. **Repro:** Open the bulk-close modal, double-click "CLOSE TICKETS" quickly → two concurrent `POST /api/bulk_operation.php` calls fire for the same ticket IDs, each posting the close-reason comment per ticket — duplicate comments/audit-log entries on every affected ticket (and duplicate Matrix notifications), even though the tickets only actually close once. **Fix:** Disable the confirm button (and/or set an in-flight flag) immediately on click, re-enabling only on request completion/error.
jared added the concurrencypriority/mediumux labels 2026-09-08 10:15:44 -04:00
Author
Owner

Fixed in commit 3fcd1cb (merged to main in 4aa83ff).

The 4 bulk-action confirm buttons (close/assign/priority/status) called their performBulk*() handler directly on click with no in-flight guard, so a double-click fired two concurrent POST /api/bulk_operation.php requests for the same ticket IDs — duplicating the close-reason comment, audit-log entry, and Matrix notification on every affected ticket.

Fix: each performBulk*() function now takes the clicked button, no-ops if it's already disabled, disables it before firing the request, and re-enables it in .finally() regardless of outcome.

Verified by extracting performBulkAssign() from the real source and driving it with a mock lt.api.post() that never resolves until told to: a simulated rapid double-click fired exactly one request (the second call was a no-op while the button was disabled), and a subsequent click after the request resolved correctly fired a new request.

Fixed in commit 3fcd1cb (merged to main in 4aa83ff). The 4 bulk-action confirm buttons (close/assign/priority/status) called their performBulk*() handler directly on click with no in-flight guard, so a double-click fired two concurrent POST /api/bulk_operation.php requests for the same ticket IDs — duplicating the close-reason comment, audit-log entry, and Matrix notification on every affected ticket. Fix: each performBulk*() function now takes the clicked button, no-ops if it's already disabled, disables it before firing the request, and re-enables it in .finally() regardless of outcome. Verified by extracting performBulkAssign() from the real source and driving it with a mock lt.api.post() that never resolves until told to: a simulated rapid double-click fired exactly one request (the second call was a no-op while the button was disabled), and a subsequent click after the request resolved correctly fired a new request.
jared closed this issue 2026-09-11 21:58:59 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/tinker_tickets#36