-
-
Notifications
You must be signed in to change notification settings - Fork 27
Issue 120 Workflow Notes Could Never Be Written
Reported: #120 by Kraleemil, August 2026 Β· Module: Workflows Β· Fixed in: #1391
A workflow with an Add a note to the ticket action fails on every run, and the run history shows:
Action 1 (add_ticket_note): SQLSTATE[23000]: Integrity constraint violation:
1048 Column 'analyst_id' cannot be null
Not intermittently, and not on particular tickets. Every time, on every installation, since the action was written.
The engine writes the history entry with no analyst against it, on purpose, and its own comment says so:
// Use the same `ticket_audit` table the rest of the app writes to
// for analyst-visible activity. analyst_id is null to flag this as
// a workflow-engine-driven note (so the UI can render it differently
// if it wants to).
$conn->prepare(
"INSERT INTO ticket_audit (ticket_id, analyst_id, field_name, old_value, new_value, created_datetime)
VALUES (?, NULL, 'Workflow Note', NULL, ?, UTC_TIMESTAMP())"
)->execute([$ticketId, $note]);That is a reasonable design: a history entry made by automation genuinely has no person behind it, and marking it as such lets the screen say so.
The column was NOT NULL.
`analyst_id` INT NOT NULL,So the intent and the schema contradicted each other, and the database won. There was no configuration under which this action could have succeeded.
π The comment is what makes this worth reading. It is not a mistake in the sense of a typo or an oversight in the logic β the author decided what NULL should mean here and wrote it down. What never happened was checking that the column agreed.
The natural first thought is that it must be about tickets with nobody assigned. It is not, and the distinction matters:
| column | means |
|---|---|
ticket_audit.analyst_id |
who performed the action |
tickets.assigned_analyst_id |
who the ticket is assigned to β and already NULL-able |
An unassigned ticket was never relevant. What matters is whether there is somebody doing the thing.
All eleven writers of ticket_audit were checked:
-
nine take a signed-in person from the session.
api/tickets/log_ticket_audit.phprefuses the request outright if there is no session, so from the UI the column is populated by definition β you cannot reach the endpoint without being signed in; -
includes/calendar_sync/pull.phpruns from cron, but attributes the change to the analyst whose calendar entry moved, which is a real person; -
workflow/includes/engine.phpis the only writer with no actor at all, because a workflow fires from a trigger with no session behind it.
So this was only ever going to surface in one place, and adding notes by hand β including to unassigned tickets β was always fine.
The column is relaxed to allow it:
`analyst_id` INT NULL,Safe in a way that tightening never is: every existing row already has an analyst, so a looser rule cannot invalidate one. The fk_ticket_audit_analyst foreign key is unaffected β a NULL never violates a foreign key, it simply has nothing to check. Both screens that read ticket history already LEFT JOIN the analysts table, so an entry without one was always going to appear.
Two places, because installations arrive by two routes:
-
database/freeitsm.sqlβ new installations -
api/system/db_verify.phpβ existing ones, following the five probe-then-MODIFY precedents already in that file
Existing installations pick this up by running System β Database Verification, which reports it in plain English:
analyst_id: NOT NULL -> NULL (a workflow writes history entries that no analyst made)
With the column fixed, the entry appears in the ticket's history with a blank author, and the screen rendered that as Unknown.
"Unknown" says we do not know when the truth is nobody did it β and it hides the two cases worth telling apart: an entry written by automation, and one written by somebody who has since left the organisation. The notes list already makes exactly this three-way split, so the history now makes it too:
| case | shown as |
|---|---|
| a real person | their name |
analyst_id IS NULL β a workflow wrote it |
System |
| a real id with no row left β they have left | Former analyst |
No new translation strings: both labels already existed for the notes list and are already translated into all 24 languages.
| File | What |
|---|---|
π₯ database/freeitsm.sql
|
ticket_audit.analyst_id becomes NULL-able |
π₯ api/system/db_verify.php
|
the same change for existing installations, reported in plain English |
π¨ api/tickets/get_ticket_audit.php
|
says which of analyst / system / former an entry is |
π© assets/js/inbox.js Β· assets/js/mobile.js
|
print that, instead of "Unknown" |
The reporter's condition was recreated on a development database and the whole path driven, with every insert inside a transaction that was rolled back, so nothing persisted:
1. tighten the column to NOT NULL -> nullable = NO
2. run the action's exact INSERT -> FAILED: SQLSTATE[23000] ... 'analyst_id' cannot be null
3. run Database Verification -> REPORTED: analyst_id: NOT NULL -> NULL
4. nullable = YES
5. run the action's exact INSERT -> SUCCEEDED
6. NULL rows left behind: 0 total rows: 160 (unchanged)
Step 2 is the one that matters: it reproduces the reported error byte for byte before the fix, which is what makes step 5 mean something. Row counts identical at the end, so the check left nothing behind.
If you have a workflow with an Add a note to the ticket action, it will start working once you have pulled the update and run System β Database Verification. Entries it writes appear in the ticket's History tab, attributed to System.
Nothing changes for entries made by people, and nothing needs re-running for history already recorded.
Issue #120 also reports that workflow notification emails create new tickets rather than threading onto the existing one. That is a separate problem and is not fixed by this change β it is still being investigated, and so far has not been reproducible on a correctly configured installation. See the issue for the current state.
- Workflows
- Database Integrity β why Database Verification is worth running after every update
- Bugs resolved
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96