Is your feature request related to a problem?
Audit log entries frequently show an empty User column, with no indication of who or what made the change. A customer raised this on SaaS: "Sometimes no user is listed in the user section in the audit log. Do you know why this happens?"
There are three ways an entry ends up with author = null:
- The change was made with a Master API Key (Terraform provider, CI scripts, direct Admin API calls).
AuditLog has a separate master_api_key FK alongside author, and it is populated correctly - we just never return it. AuditLogListSerializer and AuditLogRetrieveSerializer only expose author, so the API response has no attribution at all for these entries.
- System events (
is_system_event=True), e.g. a scheduled Change Request going live via create_feature_state_went_live_audit_log. These legitimately have no human author, but the UI gives no signal that this is why the field is blank.
- The user was deleted.
author is on_delete=models.SET_NULL, so removing a member from the organisation retroactively blanks their name on every historical entry they created.
In the UI this is worse than in the API, because AuditLog.tsx renders {author?.first_name} {author?.last_name} with no fallback - so all three cases render as an empty cell that looks like missing data rather than a known state.
The impact is that the audit log stops being a reliable answer to "who changed this flag", which is its primary job. For customers using it for compliance or for tracing an unexpected production change, a blank cell is indistinguishable from a bug. Note that is_system_event is already returned by the API and unused by the row rendering, and master_api_key attribution already exists in the database - so in both cases the information is present and simply not surfaced.
Describe the solution you'd like.
Never render an empty User cell. Specifically:
- Add the Master API Key to the audit log serializers (at minimum
{id, name} from MasterAPIKey) so API consumers can attribute API-driven changes.
- In
AuditLog.tsx, fall back through author -> master API key name (badged as an API key, e.g. 🔑 terraform-prod) -> System when is_system_event is true.
- For deleted users, show something explicit such as
Deleted user rather than nothing. This one needs a data decision - see alternatives.
Item 1 and 2 are small and independent of item 3, and together cover the majority of reported blanks.
Describe alternatives you've considered
- Denormalise the author's email onto
AuditLog at write time. This solves case 3 properly (the historical record keeps the identity of whoever made the change, even after the user is deleted) but is a larger change and has GDPR implications for erasure requests, so it deserves its own discussion. Rendering Deleted user is the cheaper option and at least distinguishes "we don't know" from "no human did this".
- Leave the API as-is and fix only the frontend. Rejected: customers query the audit log API directly for SIEM and compliance pipelines, and the attribution gap exists there too.
- Document the behaviour instead of changing it. Doesn't help - the reason a user asks about this is that they need to know who made a specific change, and documentation doesn't give them the answer.
Additional context
Relevant code:
api/audit/models.py - AuditLog.author (SET_NULL), AuditLog.master_api_key, AuditLog.is_system_event
api/audit/serializers.py - AuditLogListSerializer / AuditLogRetrieveSerializer field lists, neither includes master_api_key
api/audit/tasks.py - _create_feature_state_audit_log_for_change_request creates is_system_event=True entries with no author
frontend/web/components/AuditLog.tsx - renders {author?.first_name} {author?.last_name} with no fallback
Worth noting that the flag change webhook already handles this correctly: trigger_feature_state_change_webhooks in api/features/tasks.py falls back to master_api_key.name for changed_by. So a customer receiving webhooks currently gets better attribution than the same customer reading the audit log UI. Aligning the audit log with that existing behaviour seems like the obvious baseline.
Possibly relevant to #6304 (Epic: Audit Log Enhancements).
Raised from a SaaS customer support conversation.
Is your feature request related to a problem?
Audit log entries frequently show an empty User column, with no indication of who or what made the change. A customer raised this on SaaS: "Sometimes no user is listed in the user section in the audit log. Do you know why this happens?"
There are three ways an entry ends up with
author = null:AuditLoghas a separatemaster_api_keyFK alongsideauthor, and it is populated correctly - we just never return it.AuditLogListSerializerandAuditLogRetrieveSerializeronly exposeauthor, so the API response has no attribution at all for these entries.is_system_event=True), e.g. a scheduled Change Request going live viacreate_feature_state_went_live_audit_log. These legitimately have no human author, but the UI gives no signal that this is why the field is blank.authorison_delete=models.SET_NULL, so removing a member from the organisation retroactively blanks their name on every historical entry they created.In the UI this is worse than in the API, because
AuditLog.tsxrenders{author?.first_name} {author?.last_name}with no fallback - so all three cases render as an empty cell that looks like missing data rather than a known state.The impact is that the audit log stops being a reliable answer to "who changed this flag", which is its primary job. For customers using it for compliance or for tracing an unexpected production change, a blank cell is indistinguishable from a bug. Note that
is_system_eventis already returned by the API and unused by the row rendering, andmaster_api_keyattribution already exists in the database - so in both cases the information is present and simply not surfaced.Describe the solution you'd like.
Never render an empty User cell. Specifically:
{id, name}fromMasterAPIKey) so API consumers can attribute API-driven changes.AuditLog.tsx, fall back throughauthor-> master API key name (badged as an API key, e.g.🔑 terraform-prod) ->Systemwhenis_system_eventis true.Deleted userrather than nothing. This one needs a data decision - see alternatives.Item 1 and 2 are small and independent of item 3, and together cover the majority of reported blanks.
Describe alternatives you've considered
AuditLogat write time. This solves case 3 properly (the historical record keeps the identity of whoever made the change, even after the user is deleted) but is a larger change and has GDPR implications for erasure requests, so it deserves its own discussion. RenderingDeleted useris the cheaper option and at least distinguishes "we don't know" from "no human did this".Additional context
Relevant code:
api/audit/models.py-AuditLog.author(SET_NULL),AuditLog.master_api_key,AuditLog.is_system_eventapi/audit/serializers.py-AuditLogListSerializer/AuditLogRetrieveSerializerfield lists, neither includesmaster_api_keyapi/audit/tasks.py-_create_feature_state_audit_log_for_change_requestcreatesis_system_event=Trueentries with no authorfrontend/web/components/AuditLog.tsx- renders{author?.first_name} {author?.last_name}with no fallbackWorth noting that the flag change webhook already handles this correctly:
trigger_feature_state_change_webhooksinapi/features/tasks.pyfalls back tomaster_api_key.nameforchanged_by. So a customer receiving webhooks currently gets better attribution than the same customer reading the audit log UI. Aligning the audit log with that existing behaviour seems like the obvious baseline.Possibly relevant to #6304 (Epic: Audit Log Enhancements).
Raised from a SaaS customer support conversation.