fix(log): stabilize hstore audit snapshots - #821
Open
inkSence wants to merge 3 commits into
Open
Conversation
Install development dependencies in an isolated container virtualenv and declare python-ldap directly.
Retry HStore adapter registration after migrations and consistently exclude configured fields from audit snapshots.
Exercise adapter retries, reconnections, database errors, HStore round-tripping, rendering, and password exclusion.
inkSence
marked this pull request as ready for review
July 17, 2026 19:10
Theophile-Madet
requested changes
Jul 27, 2026
Theophile-Madet
left a comment
Collaborator
There was a problem hiding this comment.
On the Hstore fix:
- I'm not sure this is something we should fix in our code, it looks more like a bug in
django.contrib.postgres. Have you looked for a ticket in https://code.djangoproject.com/query? - Keep your PR description short, this is way too long.
On the other changes:
- Focus on the Hstore fix in this PR and do the other changes in separate dedicated PRs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix intermittent HStore audit log failures
Summary
This PR fixes the intermittent HStore failures reported in #272. It makes HStore adapter registration recover when the web process opens a PostgreSQL connection before the migration process creates the
hstoreextension.The fix re-checks HStore availability for newly created connections and at the start of later requests. Once registration succeeds, it is remembered for the lifetime of the underlying raw database connection.
The PR also contains additional changes: it fixes audit snapshot field-exclusion handling so that sensitive fields such as passwords are not persisted, and it adjusts the development Docker environment so the regression tests can run with the required development dependencies.
Fixes #272.
Reproduction before the fix
We reproduced the problem with newly created Docker volumes. Following the README startup order, the web process was allowed to connect to PostgreSQL before migrations were applied. This reproduces the timing condition that leaves the web process with a stale HStore OID lookup.
Using generated synthetic test data, we then followed the reported workflow:
+12125552368.Before the fix, saving the audited change failed. In our reproduction, the exception was:
The relevant part of the traceback was:
This failure happened while saving the audit log, before reaching the original read-side symptom from #272:
Both symptoms have the same underlying cause: the PostgreSQL connection used by the web process did not have working HStore type handlers.
Root cause
A Django web process may open its PostgreSQL connection before migrations create the
hstoreextension. During connection initialization, Django then caches an empty HStore OID lookup and cannot register the HStore adapters.Creating the extension later clears that cache only in the migration process. The already running web process continues using its existing connection without HStore adapters.
The
connection_createdsignal alone cannot recover this connection: it was already emitted when the connection was first opened, before HStore existed, and the same raw connection may remain open after the migration.As a result, dictionaries cannot be adapted when writing an
HStoreField, or HStore values are decoded as strings when reading them.Fix
connection_created.request_started, allowing a connection that was opened before the migration to recover on a later request.The request-start check is inexpensive for an already registered raw connection: the connection is marked as registered, so later checks of the same raw connection return without repeating the OID lookup or adapter registration.
The fix is applied at the database connection boundary. It does not add a defensive JSON or string decoder that could conceal an incorrectly configured HStore connection.
Related changes
Audit snapshot field exclusion
Field exclusion happens at two points:
freeze_for_log()readsexcluded_fields_for_logsfrom the domain model. This now correctly removes fields such asTapirUser.passwordbefore the snapshot is returned.ModelLogEntry.populate_base()now recognizes bothexcluded_fieldsandexclude_fieldswhen populating a log entry'svaluessnapshot.Both paths use non-failing removal so that a configured field does not cause an error when it is absent from the snapshot.
Development environment
The Docker development setup now keeps the container's virtual environment separate from a host-side
.venvand installs the development dependencies required to run the focused regression tests. The project also declares its directpython-ldapdependency explicitly.These environment changes support local verification but are independent of the runtime HStore registration fix.
Verification
Focused automated tests
The focused regression suite passed:
It covers:
The test command was:
docker compose exec -T web poetry run pytest \ tapir/log/tests/test_apps.py \ tapir/accounts/tests/test_update_tapir_user_log_entry.py -qFresh-container startup test
We removed the existing Docker volumes and recreated the services from scratch. The web process was deliberately allowed to connect to PostgreSQL before migrations were applied. We then applied all migrations and generated synthetic test data without restarting the web process.
The first request after the migration reached the login form successfully, and the web container's restart count remained zero. This confirms that the already running process continued serving requests without requiring a restart. The subsequent audited profile update verified that HStore registration had recovered. Depending on the database connection lifecycle, registration occurs either when
request_startedre-checks an existing raw connection or whenconnection_createdinitializes a replacement connection.Manual browser verification
We then repeated the original audited profile-update workflow:
+12125552368as the phone number.The save completed successfully. The request returned a redirect instead of an HTTP 500 response, and the web logs contained no
ProgrammingError. This confirms that the audited HStore write succeeded in the web process that had been started before the migration.We additionally inspected the persisted audit entry and confirmed:
Manual inspection of the persisted entry confirmed that both HStore fields were decoded as dictionaries. The focused integration test additionally reloads and renders the entry, covering the original read-side failure.
Formatting and targeted lint checks passed.
manage.py checkonly reported the pre-existing warning about the missing developmentdistdirectory, andpoetry check --lockpassed with its pre-existing license deprecation warning.