FAQ & Troubleshooting
Answers to the questions we hear most, plus concrete fixes for the problems that actually happen in production — organized pillar by pillar, and grounded in how the Helpdesk module truly behaves.
Troubleshooting quick-reference
Start here. Scan for your symptom, then jump to the detailed answer in the section below.
| Symptom | Likely cause | Fix |
|---|---|---|
| A pillar doesn't appear in the left menu | Module toggle off, or the role lacks the pillar permit (access = both) | Enable the toggle in Settings → Manage Modules and grant the permit in the role |
| Helpdesk permission rows are missing from the Roles editor | module_helpdesk is disabled | Enable module_helpdesk; the rows surface only when the master is on |
| A client can't see a ticket they expect | Ticket isn't theirs / they're not a collaborator, or the pillar toggle is off | Confirm the requester/collaborator and that module_ticket is enabled |
| Email replies aren't creating or updating tickets | Email piping / IMAP not authorized, ext-imap missing, or run_imap not scheduled | Enable piping + authorize IMAP, install ext-imap, schedule the cron — see Email & Automation |
/knowledge_base returns 404 for everyone | module_knowledge_base is disabled (existence is hidden) | Enable module_knowledge_base |
| A guest can't open a KB article | Article is a draft (not active), or the module is off | Publish the article to active; confirm the module toggle |
| Internal Wiki content is visible to the public | It shouldn't be — Wiki (type = help) is staff-only by design | Publish public content as a KB article (type = knowledge_base) instead |
| A handbook upload is stuck / never produces drafts | Cron not running, AkbAI not configured, scanned PDF (no OCR), or max attempts hit | Schedule the cron, configure AkbAI, or add sections manually |
| An import shows "Needs review" | Scanned/image PDF, empty PDF, or processing stopped after repeated failures | Open the review page and add/adjust sections by hand |
| Employees can't see a handbook article | It's still a draft, not published | Approve & publish it (requires handbook_publish) |
| The URL Shortener returns "disabled" (403) on mint | An operator turned the tool off (it ships active/public by default) | Re-enable the url-shortener tool in the Tools Hub; the /s/{code} resolver stays public either way |
An API call returns missing_company_key / 401 | Missing tenant company key or invalid/absent bearer token | Send the company key and a valid JWT — see Developer APIs |
Where to look
Which pillar owns which task, and the page that documents it in depth.
Tickets
Statuses, assignment, SLA/due dates, labels, the client & staff lifecycle.
Email & Automation
IMAP-to-ticket ingestion, the run_imap and close_inactive_tickets cron jobs.
Knowledge Base
The public read surface: categories, published articles, guest browsing.
Internal Wiki
Staff-only articles (type = help) and category management.
Pages & Help Center
Web Pages, public /about/{slug} renderer, and short-link sharing.
Company Handbook
AkbAI PDF ingestion, HR review, employees-only reader.
General & access
The questions that apply across every pillar — toggles, permits, and how the two combine.
A sidebar entry renders only when both gates pass: the module toggle is enabled and the signed-in user's role grants the item's permit. The menu is filtered exactly this way — for example the Handbook entry (menu key handbook) is gated by module_enabled("module_helpdesk") plus the handbook permit. If either is missing, the item is silently dropped.
Checklist:
- Settings → Manage Modules — is the pillar's toggle on (
module_ticket,module_help,module_knowledge_base,module_page) and is the mastermodule_helpdeskon where required? - The user's role — does it grant the pillar permit (
ticket,help/knowledge_base,page,handbook)?
module_helpdesk is the umbrella master toggle for the whole module (it is seeded on by a core migration, because settings is a shared per-tenant table). Under it sit four pillar sub-toggles that turn each capability on or off independently:
Important nuance during the current cutover: the pillar controllers still gate access on their own legacy toggles — Tickets on module_ticket, Internal Wiki on module_help, Knowledge Base on module_knowledge_base, and Pages on module_page. The Handbook reader gates on module_helpdesk directly. So to open a specific pillar, make sure its sub-toggle is on; the master governs which permission rows appear in Roles and the module as a whole.
All Helpdesk permission definitions live in modules/Helpdesk/config/permissions.php and are surfaced into the RBAC editor only when module_helpdesk is enabled. That's why the ticket*, help*, knowledge_base*, page* and handbook* rows vanish when the master is off.
users.permissions regardless of where they're defined. Toggling the module off then on again does not reset existing role grants — it only hides/shows the editable rows.Only the Knowledge Base is a public read surface. When module_knowledge_base is enabled, an anonymous visitor can browse published KB categories and articles with no permit at all — the controller runs under App\Guards\OptionalAuthGuard, so a guest is resolved as a guest instead of being bounced to sign-in. Every other pillar (Tickets, Internal Wiki, Pages admin, Handbook) requires a logged-in user with the matching permit.
Tickets
Statuses, visibility, SLA/due dates, labels, reassignment, and the auto-close caveat.
What do the ticket statuses mean?
| Status | Meaning |
|---|---|
new | Just created, not yet worked. Both new and open tickets are candidates for auto-close. |
open | Actively being handled by an agent. |
client_replied | The requester has responded and the ticket is awaiting the agent again. |
on_hold | Paused (e.g. waiting on a third party). Not actively worked. |
closed | Resolved / no further action. Sets closed_at. |
Clients only see tickets scoped to them — those they raised, or where they're a listed collaborator. If a client reports a missing ticket, verify: (1) module_ticket is enabled, (2) the client is the requester or a collaborator on that ticket, and (3) the client actually has the ticket_create/access permit through their client role. A staff agent viewing another user's ticket needs the ticket_staff access scope for that owner.
SLA is derived from the ticket type. Each ticket type carries a duration (in seconds) — the built-in choices are 1 hr (3600), 2 hr (7200), 4 hr (14400) and 8 hr (28800). When a ticket is created with a type, that duration seeds its due date. An agent with the override_ticket_due_date permit can set a custom due date, and override_ticket_type lets a user change the type on an existing ticket.
Labels (including priority) are stored on the ticket as a comma-separated labels value and matched with FIND_IN_SET — there is no separate priority column in this version. Adding a new label from the ticket form requires the add_ticket_label permit; without it, an agent can pick existing labels but not create new ones.
Open the ticket and change its assignee via the Assigned to dropdown. Reassignment is gated by the manage capability — a user needs ticket_manage to change assignment and other ticket-management fields. Collaborators (the ticket_collaborator scope) can be added so additional people follow the thread without owning it.
The close_inactive_tickets cron job (default schedule 30 2 * * *) auto-closes tickets when the auto_close_ticket_after setting (in days) is configured. It selects tickets in new or open status, writes a system comment (ticket_auto_closed_due_to_inactivity), sets status to closed with closed_at, and logs a ticket_closed notification.
created_date_or_before), not a true last-activity timestamp. A long-running but still-active ticket created before the threshold can therefore be auto-closed. If auto-close is closing tickets you consider active, disable auto_close_ticket_after or lengthen it until the last-activity fix lands.If auto_close_ticket_after is empty, the job simply skips and logs that it was not set.
Email-to-ticket
The most common "email isn't working" causes — all of which live in Tickets — Email & Automation.
Inbound mail is polled by the run_imap cron job. It skips unless both enable_email_piping and imap_authorized settings are on, and it self-throttles to once every 10 minutes. Walk this list:
- IMAP settings: enable email piping and authorize the mailbox so
imap_authorizedis set. ext-imap: the PHPimapextension must be installed and loaded — theddeboer/imaplibrary needs it at runtime.- Cron scheduled: the
run_imapjob (default schedule every minute) must actually be firing via the cron runtime. If it isn't scheduled, nothing polls the inbox. - Throttle window: even when scheduled every minute, the job only does real work once per 10-minute window — allow time before concluding it's broken.
Knowledge Base & Internal Wiki
Both pillars share the help_articles / help_categories tables, discriminated by a type column: knowledge_base (public) vs help (the staff-only Internal Wiki).
The controller calls show_404() up front whenever module_knowledge_base is not enabled — a disabled module hides its existence rather than showing a "disabled" page. Enable module_knowledge_base and the route resolves again. (Note: a logged-in user additionally needs the knowledge_base permit to see the in-app management experience; a guest reads the published KB with no permit.)
Two reasons. Either the module is off (see above), or the article/category isn't published. Guests are limited to active content: a category whose status isn't active, or an article whose status isn't active, returns 404 for an anonymous visitor. Logged-in staff can still preview drafts; guests cannot. The same rule guards file downloads — a guest may only download files attached to an active KB article.
The Internal Wiki is staff-only by design. Articles with type = help are served by the Help controller, which requires a logged-in user with the help (or knowledge_base) permit and the module_help toggle — a guest is redirected, never served. Only articles with type = knowledge_base are exposed on the public Knowledge Base.
So if you want an article to be internet-visible, it must be a KB article (type = knowledge_base) and published to active. Creating it as a Wiki (help) article will never make it public, no matter its status.
All KB authoring (create/edit/delete of articles and categories) lives in the permission-gated Help controller and its authenticated tab-panels — not on the public Knowledge_base route. A guest hitting a management tab-panel gets a 404. The permits are knowledge_base_create / _update / _delete for articles and knowledge_base_category_create / _update / _delete for categories. See Permissions Reference.
Company Handbook
The AkbAI-assisted PDF ingestion pipeline, and why an import might stall or land in review.
Import statuses you'll see on the admin review screen:
Processing happens asynchronously in the helpdesk_process_handbook_imports cron job. Common causes, in order of likelihood:
- Cron not running. If the job isn't scheduled/firing, uploads sit in
Queuedforever. Confirm the cron runtime is executing it. - AkbAI not configured. AI sectioning needs
AKBAI_HOST/AKBAI_KEY. Without them the pipeline still extracts and drafts articles from the cleaned text for manual HR editing — it doesn't hang, but you won't get AI-shaped sections. - Scanned / image-only PDF. There's no OCR in this version. A scan can't be read, so it's flagged Needs review for manual entry.
- Too many failed attempts. The job bounds retries with an
attemptscounter and aMAX_ATTEMPTScap; after repeated failures it stops re-picking the row (this deliberately kills the old every-5-minute reprocessing loop). Re-upload or add sections manually.
Needs review means the pipeline couldn't confidently turn the PDF into sections and is handing it to HR to finish. The usual triggers: the PDF is scanned/image-only (no readable text, no OCR), the PDF is effectively blank/empty, or processing was stopped after repeated failures. Open the review page, read the error_message shown there, and add or adjust the sections by hand.
The employee-facing reader only shows published articles. Drafts are invisible to employees until an HR user approves and publishes them. If the reader looks empty, the handbook simply hasn't been published yet. The reader is also gated by module_helpdesk + the handbook permit, so a user without that permit won't see it at all.
Editing a draft requires handbook_update, but editing an article that is already published to employees additionally requires handbook_publish. This guards live, employee-visible policy content: publishing (and re-editing what's published) is a higher-trust action than editing an unpublished draft. Upload/import needs handbook_create; deletion needs handbook_delete.
1. Confirm the cron is alive
Verify helpdesk_process_handbook_imports is scheduled and running. A dead cron is the #1 cause of a perpetual Queued.
2. Read the review reason
Open the import's review page and read the error_message — it names the exact problem (unreadable, scanned, blank, or repeated failure).
3. Re-upload a born-digital PDF
If it was a scan or corrupt, re-upload a text-based PDF. If AkbAI is unconfigured, drafts still generate from the cleaned text for manual editing.
4. Add sections by hand if needed
For scans or blank PDFs, add the sections manually on the review screen, then approve & publish (requires handbook_publish).
Pages & short links
Web Pages, public sharing, and the tenant-isolation guarantee behind shared links.
Yes. In the current source the URL Shortener ships active · public and free — it mints /s/{code} short links from the Tools Hub. An operator can disable the tool (minting then returns 403), but the /s/{code} resolver is always public. The standard Sharable Link Generator is also available and reuses the existing shareable-result generator.
/s/{code} resolver and the tool_shortlinks store) is owned by the Tools module, not Helpdesk. The Pages "Share" action consumes it.A shared link is a short opaque code of the form /s/{code} (a 7-character base-56 token — an unambiguous alphabet that omits 0/O/1/l/I). The URL carries only the code — no tenant slug, no page id, nothing decryptable from the path. The tenant's company key is stored encrypted at rest in tool_shortlinks (primary DB). On resolve, the code is looked up, the tenant key is decrypted, the correct tenant DB is switched in, Open Graph tags are emitted for the unfurl, and the target is rendered or redirected. A bad, disabled, or expired code returns 404 to hide existence.
This design means a shared page always loads the right tenant's data and never leaks tenant identifiers in the URL.
Pages is the standalone Web Pages capability (staff admin at pages, with a guest-facing per-slug renderer at about/{slug}), gated by module_page + the page permit. The public Help Center front door (help-center) renders a CMS-themed landing via the separate Content (CMS) module — a soft dependency: if Content/CMS is unavailable it 404s gracefully rather than disabling all of Helpdesk. Helpdesk reuses CMS theme chrome but does not absorb the CMS. See Web Pages & Help Center.
Developer APIs
Authentication, scopes, and the flat-controller entry points.
Every Helpdesk API call runs through the tenant + JWT + scope middleware pipeline before the controller body. missing_company_key means the request didn't include the tenant company key the multi-tenancy layer needs to resolve the right database. A 401 means the bearer token is missing, invalid, or expired. Send both a valid company key and a valid JWT bearer token. Full request shape is on Developer APIs.
The integration (client) Help Center API is read-only under scope help:read, exposing GET api/v1/help/categories, api/v1/help/categories/{id}, api/v1/help/articles and api/v1/help/articles/{id}.
The End-User API is per-employee. GET v1/api/me/help-articles is org-wide read (any authenticated employee, no extra permit — the Help Center is shared reference content, not personal data). The Tickets End-User endpoints are self-only — an employee sees and writes only their own tickets: GET v1/api/me/tickets, POST v1/api/me/tickets, GET v1/api/me/tickets/{id}, and POST v1/api/me/tickets/{id}/messages.
Yes — and it's expected. Because module-owned API controllers must be flat, uniquely-named top-level classes, they are also reachable via their bare class path (/helpdesk_api/* and /helpdesk_euapi/*) in addition to the documented api/v1/help/* and v1/api/me/* URLs. This is not an auth bypass: the same tenant + JWT + scope middleware runs from the base controller regardless of which URL reached the class. It's simply an inherent side effect of the flat-controller requirement.
Still stuck?
If nothing above matches, work through this quick escalation path before filing a report.
Check both gates
Ninety percent of "missing" reports are a module toggle off or a role permit not granted. Re-verify both — see Permissions Reference.
Read the on-screen reason
Handbook review pages surface an error_message; API responses name the failure. The system usually tells you what's wrong.
Confirm the crons are running
Email ingestion (run_imap), auto-close (close_inactive_tickets), and handbook processing (helpdesk_process_handbook_imports) all depend on a live cron runtime.
Consult the pillar page
Each pillar's dedicated page (see Overview) documents its full workflow, statuses, and edge cases in depth.