Skip to content

auth: ensure every User has a UserAuth row + raise asyncio slow-callback threshold#712

Merged
ajslater merged 1 commit intov1.11-performancefrom
claude/userauth-autocreate-and-asyncio-threshold
May 4, 2026
Merged

auth: ensure every User has a UserAuth row + raise asyncio slow-callback threshold#712
ajslater merged 1 commit intov1.11-performancefrom
claude/userauth-autocreate-and-asyncio-threshold

Conversation

@ajslater
Copy link
Copy Markdown
Owner

@ajslater ajslater commented May 4, 2026

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:

WARNING | BookmarkThread | No UserAuth row for user pk=1; skipping touch.

…on every active user (typically the createsuperuser-provisioned admin).

The fix establishes a single invariant: every User has exactly one UserAuth. Three pieces:

  • New post_save signal in codex/signals/django_signals.py does UserAuth.objects.get_or_create(user=instance) whenever a User is created, 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). Folded into 0039 rather than a separate 0040 since v1.11-performance is still alpha.
  • perform_create and 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 "table is empty" assertion was flipped to assert the new invariant (UserAuth.count() == User.count()).

The update_user_active warning 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.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 (sync Django view on a thread, awaited from the loop), and a DB-heavy view crossing 100ms is routine, not an anomaly:

WARNING | MainThread | Executing <Task pending name='Task-11'
  coro=<AsyncToSync.main_wrap() running at asgiref/sync.py:365> …> took 0.298 seconds

These drown out genuine slow-task signal. Bump loop.slow_callback_duration = 5.0 in _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.

Test plan

  • pytest tests/ — 26 passed (was failing on MigrationShapeTestCase::test_user_auth_table_exists_and_is_empty until I updated that test to match the new invariant)
  • make lint-python clean
  • make ty clean (basedpyright + ty both)
  • Verify against a fresh dev server: previous "No UserAuth row for user pk=1" warnings stop, and the asyncio "took N seconds" warnings only fire for callbacks above 5s
  • Verify against an existing production DB after migration: the 0039 backfill runs, every User has exactly one UserAuth

🤖 Generated with Claude Code

…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>
@ajslater ajslater merged commit d33435f into v1.11-performance May 4, 2026
1 check failed
@ajslater ajslater deleted the claude/userauth-autocreate-and-asyncio-threshold branch May 4, 2026 02:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant