auth: ensure every User has a UserAuth row + raise asyncio slow-callback threshold#712
Merged
ajslater merged 1 commit intov1.11-performancefrom May 4, 2026
Conversation
…ack threshold Two fixes bundled, both surfacing as noisy WARNINGs in dev mode. ## 1) UserAuth invariant UserAuth (renamed from UserActive in 0039) was only provisioned by ``AdminUserViewSet.perform_create``. Anything else — ``manage.py createsuperuser``, fixtures, factory_boy — left the User without a matching row, so the bookmark thread's hourly activity touch fired "No UserAuth row for user pk=N; skipping touch." on every active user. The fix is a single invariant: every ``User`` has exactly one ``UserAuth``. Three pieces: - New ``post_save`` signal in :mod:`codex.signals.django_signals` ``get_or_create``s a default ``UserAuth`` for every new User, so every creation path now satisfies the invariant. - 0039 picks up a ``RunPython`` step that backfills a default row for any pre-existing User missing one (the legacy lazy-create ``UserActive`` gap, plus any ``createsuperuser``-provisioned admin). Folded into 0039 rather than a separate 0040 since the branch is still alpha. - ``perform_create`` and the related ``UserSerializer._apply_userauth`` rely on the row pre-existing now: ``perform_create`` only patches the admin-supplied ``age_rating_metron`` ceiling onto the signal-created row. Tests that explicitly created their own ``UserAuth`` row alongside a ``User`` were updated to either drop the redundant create or patch the auto-created row, and the ``MigrationShapeTestCase`` "empty table" assertion was flipped to assert the new invariant. The ``update_user_active`` warning intentionally stays — at this point a missing row really would mean a real data-integrity issue worth surfacing. ## 2) asyncio slow-callback threshold ``bin/dev-server.sh`` exports ``PYTHONDEVMODE=1`` when DEBUG=1, which flips asyncio into debug mode. The default ``slow_callback_duration`` of 100ms is calibrated for pure-async codebases — Codex routes most requests through asgiref's ``AsyncToSync`` (a sync Django view on a thread, awaited from the loop), and a DB-heavy view crossing 100ms is routine, not an anomaly. The warnings drown out genuine slow-task signal. Bump ``loop.slow_callback_duration`` to 5.0s in :func:`_serve` so the warning still catches truly slow callbacks (a 5s+ async operation is a real bug worth surfacing) without spamming on every normal sync-view request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Two fixes bundled, both surfacing as noisy WARNINGs in dev mode.
1. UserAuth invariant
UserAuth(renamed fromUserActivein 0039) was only provisioned byAdminUserViewSet.perform_create. Anything else —manage.py createsuperuser, fixtures, factory_boy — left the User without a matching row, so the bookmark thread's hourly activity touch fired:…on every active user (typically the
createsuperuser-provisioned admin).The fix establishes a single invariant: every
Userhas exactly oneUserAuth. Three pieces:post_savesignal incodex/signals/django_signals.pydoesUserAuth.objects.get_or_create(user=instance)whenever a User is created, so every creation path now satisfies the invariant.RunPythonstep that backfills a default row for any pre-existing User missing one (the legacy lazy-createUserActivegap). Folded into 0039 rather than a separate 0040 sincev1.11-performanceis still alpha.perform_createandUserSerializer._apply_userauthrely on the row pre-existing now:perform_createonly patches the admin-suppliedage_rating_metronceiling onto the signal-created row.Tests that explicitly created their own
UserAuthrow alongside aUserwere updated to either drop the redundant create or patch the auto-created row, and theMigrationShapeTestCase"table is empty" assertion was flipped to assert the new invariant (UserAuth.count() == User.count()).The
update_user_activewarning intentionally stays — at this point a missing row really would mean a real data-integrity issue worth surfacing, which was the original docstring rationale.2. asyncio slow-callback threshold
bin/dev-server.shexportsPYTHONDEVMODE=1when DEBUG=1, which flips asyncio into debug mode. The defaultslow_callback_durationof 100ms is calibrated for pure-async codebases — Codex routes most requests through asgiref'sAsyncToSync(sync Django view on a thread, awaited from the loop), and a DB-heavy view crossing 100ms is routine, not an anomaly:These drown out genuine slow-task signal. Bump
loop.slow_callback_duration = 5.0in_serveso the warning still catches truly slow callbacks (a 5s+ async operation is a real bug worth surfacing) without spamming on every normal sync-view request.Test plan
pytest tests/— 26 passed (was failing onMigrationShapeTestCase::test_user_auth_table_exists_and_is_emptyuntil I updated that test to match the new invariant)make lint-pythoncleanmake tyclean (basedpyright + ty both)UserAuth🤖 Generated with Claude Code