Skip to content

fix(settings): absorb the first-request browser settings create race - #1000

Merged
ajslater merged 1 commit into
developfrom
fix-settings-create-race
Oct 29, 2026
Merged

ajslater merged 1 commit into
developfrom
fix-settings-create-race

Conversation

@ajslater

Copy link
Copy Markdown
Owner

Summary

A new visitor's first page load and its API calls arrive together on one fresh session. Each request looked up the session's browser settings row, missed, and inserted one; the loser hit unique_settingsbrowser_session and answered 500:

django.db.utils.IntegrityError: UNIQUE constraint failed:
codex_settingsbrowser.session_id, codex_settingsbrowser.client, codex_settingsbrowser.name

Seen on a fresh database's first load of / (FrontendView → get_last_route → _load_browser_settings_data). The reader settings path already absorbed the same race through get_or_create; the browser path did a plain create.

Fix (codex/views/settings.py)

  • The browser row, its filters and its last route are created in one transaction.atomic() block, so a losing insert rolls back cleanly and no concurrent request can see a row without its related rows.
  • On IntegrityError, _get_or_create_settings runs the lookups again and uses the winner's row. If it still finds none, the original error is re-raised.
  • The user-then-session lookups move into _find_settings so the retry reuses them.

Tests

tests/test_settings_create_race.py reproduces the window the way the existing reader-race test does: the lookups miss once while the winner's row already exists. It covers an anonymous session, a logged-in user, and a real IntegrityError still raising. Without the fix the two race tests fail with the exact error above.

make lint, make ty and make test pass (1745 passed). Real concurrent requests against a running server were not reproduced; the test simulates the timing.

🤖 Generated with Claude Code

A new visitor's first page load and its API calls arrive together on one
fresh session. Each looked the browser settings row up, missed, and
inserted; the loser hit unique_settingsbrowser_session and answered 500.

The browser row, its filters and its last route are now created in one
atomic block, so a loser rolls back cleanly and no request sees a row
without its related rows. On IntegrityError the lookups run again and the
winner's row is used; with no row found the error is re-raised. The reader
path already absorbed the same race through get_or_create.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ajslater
ajslater merged commit 96cb8c3 into develop Oct 29, 2026
9 checks passed
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