Skip to content

Issue 79 Ticket Status Not Set

Ed Mozley edited this page Aug 19, 2026 · 1 revision

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_active filters 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_closed is 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

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally