Issue 79 Ticket Status Not Set - edmozley/freeitsm GitHub Wiki
A new ticket arrived with no status (issue #79)
A ticket created from the mail inbox had an empty Status. The column in the ticket list was blank, the Status dropdown opened with nothing chosen, and changing the status to anything else and back put it right โ permanently.
Reported in issue #79 by tjedelhauser, on a German installation.
Fixed in 03f5fc56, released as update #1111.
The same report also covered the empty Source field, which is a different fault with a different cause โ see A ticket from email did not say it came from email.
1. What you saw
The ticket opened correctly. It had a subject, a requester, a message, a priority. It simply had no status.
The tell is in the workaround. Setting the status to something else and back again fixed it for good โ which is the signature of a value that was never set, rather than one that was set wrongly. A wrong value would come back wrong.
Two things made this harder to read than it should have been.
The ticket header disagreed with the list. The reading pane said Status: Offen while the list row beside it showed no status at all. Neither looked obviously wrong, so the natural conclusion was a display glitch somewhere.
The priority was fine. The same row showed its Normal priority chip quite happily. A ticket that had lost only half of what it was created with reads like a rendering problem, not a creation problem.
2. The mechanism
Every route that opens a ticket asked the database for the status by name:
INSERT INTO tickets (โฆ, status_id, priority_id, โฆ) VALUES (
โฆ,
(SELECT id FROM ticket_statuses WHERE name = 'Open' LIMIT 1),
(SELECT id FROM ticket_priorities WHERE name = 'Normal' LIMIT 1),
โฆ
)
Open is a display name. It is listed under Tickets โ Settings โ Statuses precisely so you can rename it, and on this installation it had been renamed to Offen.
So the subquery matched nothing, returned NULL, and the ticket was filed with no status at all. No error was raised, because tickets.status_id is nullable โ as it must be, so that a status can be retired without destroying the tickets that used it.
Why the priority survived
The very same statement resolves the priority by name too, and it worked. Not because it was written differently, but because "Normal" is the same word in German. It had never been renamed, so that lookup still matched.
One statement, two identical lookups, one word of difference between them โ which is exactly why the row showed a priority chip and no status chip.
Why the header claimed a status the ticket did not have
The reading pane was doing this:
const summaryStatus = email.status || t('tickets.reading_pane.summary_open'); // "Open"
With no status stored, email.status is empty and the fallback fires. The header displayed Offen because the ticket had no status โ the translated word for the value it assumed was there.
The list row, meanwhile, was being honest. It draws its chip only when there is something to draw:
if (c && email.status) { โฆ }
So the two halves of the screen disagreed, and the half that was telling the truth was the half that looked broken.
The general lesson: a fallback that supplies the expected value hides the absence it was meant to reveal.
?? 'Open'cannot tell "no status" from "the usual status", and it answers confidently either way. The header now says None, which is what is true.
3. It was fourteen places, not one
The reported symptom came from the mail path. Sweeping for the same pattern found it everywhere a ticket can be created, and then in a second family of faults underneath.
Ticket creation โ seven paths
| Path | File |
|---|---|
| Mail collection | api/tickets/check_mailbox_email.php |
| Self-service portal | api/self-service/create_ticket.php |
| Web chat | includes/webchat/webchat.php |
| WhatsApp / Slack and other channels | includes/messaging/ingest.php |
| Service catalogue request | includes/catalogue_approvals.php |
| Workflow engine | workflow/includes/engine.php |
| The service layer โ used by the analyst's own New ticket form | includes/services/tickets.php |
A latent failure that was worse than the reported one
api/tickets/create_ticket.php defaulted the priority to the literal 'Normal', and the service layer throws on a name it cannot find:
throw new ServiceError('validation', 'invalid_field', 'Unknown priority: ' . โฆ);
So on an installation that renamed its priorities, creating a ticket by hand would not have produced a ticket without a priority. It would have failed outright with "Unknown priority: Normal". German escaped this by the same coincidence as before; a French or Spanish installation would not have.
Reopening a closed ticket
includes/ticket_reply.php preferred a status literally named Open ahead of the configured default when a customer replied to a closed ticket. It degraded gracefully, but it meant the setting marked as your default could lose to a name.
The repair that could never run
api/system/db_verify.php already contained the fix for tickets left without a status:
UPDATE tickets SET status_id = (SELECT id FROM ticket_statuses WHERE is_default = 1 LIMIT 1)
WHERE status_id IS NULL
It sat inside a block guarded by the presence of the legacy tickets.status column โ a column that only installations upgraded from a much older version still have. A fresh installation has never had it.
Which means the repair could only ever run on the installations that could not hit the bug, and never on the ones that could.
4. The fix
Every one of those paths now resolves the status from the one you have marked as the default, not from a word:
SELECT id FROM ticket_statuses
WHERE is_active = 1
ORDER BY is_default DESC, display_order, id
LIMIT 1
Two deliberate choices in that ordering:
is_activefilters rather than sorts. A deactivated status is absent from the dropdown, so starting tickets in one would reproduce this exact symptom by a different route.is_closedis deliberately not filtered. An administrator who marks a closed status as their default has said what they meant, and it is not this query's job to overrule them.
Priorities resolve the same way, from ticket_priorities.is_default.
Existing tickets are repaired by System โ Database Verify, which now runs that backfill as a standalone step outside the legacy block, and reports how many rows it fixed.
5. ๐ The files involved
๐๏ธ schema ยท ๐ read ยท โ๏ธ write ยท ๐ฅ๏ธ UI
โ๏ธ The seven creation paths
| ๐จ | File | What changed |
|---|---|---|
| โ๏ธ | api/tickets/check_mailbox_email.php |
The mail path โ the one reported |
| โ๏ธ | api/self-service/create_ticket.php |
Portal. Its priority was already validated against the active list |
| โ๏ธ | includes/webchat/webchat.php |
Web chat |
| โ๏ธ | includes/messaging/ingest.php |
WhatsApp, Slack and other channels |
| โ๏ธ | includes/catalogue_approvals.php |
Catalogue requests โ already used is_default for priority, not for status |
| โ๏ธ | workflow/includes/engine.php |
Workflow "create ticket" action |
| โ๏ธ | includes/services/tickets.php |
The service layer. Tried the name first and fell back to the default; the order is now reversed |
โ๏ธ The related faults
| ๐จ | File | What changed |
|---|---|---|
| โ๏ธ | api/tickets/create_ticket.php |
Stopped injecting 'Normal'; the key is omitted unless the form sent one |
| โ๏ธ | includes/ticket_reply.php |
Reopening prefers the configured default over a status named Open |
| ๐ฅ๏ธ | assets/js/inbox.js |
The header says None instead of inventing Open; also stopped reverting Department and Owner to English |
| ๐ | api/system/db_verify.php |
The NULL-status repair moved out of the legacy block so it can actually run |
๐๏ธ What was already right
ticket_statuses and ticket_priorities already had is_default, already enforced exactly one default on save, and already refused to delete it. The mechanism was in place the whole time; the creation paths simply did not use it.
6. How it was verified
Against a live database, with the failure reproduced first rather than assumed.
| Check | Result |
|---|---|
| Renamed Open โ Offen, ran the old subquery | NULL โ the bug, reproduced |
| Same rename, new subquery | The default status, correctly |
| Renamed Open โ Offen and Normal โ Mittel, created a ticket through the real endpoint | Ticket created as Offen / Mittel |
| Named an unknown priority explicitly | Still rejected โ validation intact |
| Named a valid priority explicitly | Honoured, not overruled by the default |
| Default status deactivated | Falls back to the first active status, never NULL |
| No row flagged default at all | Falls back to the first active status, never NULL |
The last four matter as much as the first two: it would be easy to make the failing case pass by making every case return the default, and the explicit-priority checks are what prove that did not happen.
7. What this means for you
- If you have renamed your statuses, update. Every ticket opened since you renamed them may have no status.
- Run System โ Database Verify afterwards. It gives the default status to any ticket that has none, and tells you how many it repaired.
- You can rename statuses freely. That was always the intention; it now actually holds.
- If a ticket shows no status today, changing it to anything and back still fixes that one ticket โ but Database Verify will do the lot.
Related pages
- Bugs resolved โ the index of write-ups like this one
- A ticket from email did not say it came from email โ the other half of the same report
- Never identify a lookup row by its name โ the general fault, and the eighteen further places it was found
- Renaming an impact level โ the same disease in Service Status, found earlier
- Database verification โ what the repair step does