Skip to content

fix(members): store any company name in the IDP member sync and retry on concurrent member creation - #652

Open
romanetar wants to merge 3 commits into
mainfrom
fix/idp-member-sync-utf8mb4-and-race
Open

romanetar wants to merge 3 commits into
mainfrom
fix/idp-member-sync-utf8mb4-and-race

Conversation

@romanetar

@romanetar romanetar commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

ref: https://app.clickup.com/t/9014802374/86bch3jgw

Problem

  • Member.Company is latin1. A company name with a character outside latin1 (full-width parentheses, Ł) is rejected by production's strict MySQL with 1366 Incorrect string value. The whole registerExternalUserById() transaction rolls back (name, email, verification, groups) and the attendee sync that follows never fires. The message jobs swallow the exception, so nothing retries and the same user fails on every later update.
  • Two processes can create the same member at the same time. The login path recovers from the duplicate, the IDP sync does not, so the message is lost.

Change

  • New migration converts Member.Company to utf8mb4_general_ci (latin1 is a subset, existing values convert unchanged). MySQL cannot do it in place (ALGORITHM=COPY), so writes to Member are blocked while it runs.
  • MemberService::registerExternalUserById() retries once, in a new transaction, when it fails with UniqueConstraintViolationException (ExternalUserId or Email key), and logs a warning. The second run finds the member the other process created and updates it.

Tests

New in tests/MemberServiceTest.php, all three failing on main:

  • company (ACME) and Łódź Inc through registerExternalUserByPayload(): stored as sent and first name updated (on main they read back ?ACME? and ?ód? Inc, CI runs MySQL non-strict)
  • race through registerExternalUserById() with the IDP API mocked and a repository double that misses the first lookups: on main it throws Duplicate entry ... for key 'Member.Email'

phpunit tests/MemberServiceTest.php: 6 tests, 0 failures.

Production check

Loki, last 30 days, {service_name="summit-api", k8s_cluster_name="prod-fn", container="summit-api-worker-message-broker-php"} |~ "(?i)incorrect string":

  • every error reports column Company, no other Member column fails, so the migration converts only Company.
  • 17 failed jobs (2026-09-23 to 2026-10-14), all from PublishUserCreated / PublishUserUpdated, for 10 IDP users. The same user fails again on later updates (user 100318 failed on 09-29 and again on 10-13).
  • The ExternalUserId duplicate of 2026-10-06 (IDP user 102460, PublishUserCreated) comes from the insert of a new member (MemberService.php:371), so the retry covers it: the second run finds the member the other process created.

Pending

  • After deploy, re-sync with registerExternalUserById() the IDP users whose sync failed: 96477, 100318, 104565, 97078, 91396, 91711, 100767, 100066, 99199, 98654 and 102460 (duplicate). Run it after the migration, otherwise they fail again with the 1366.
  • After deploy, check that the Loki query for Incorrect string value returns no lines over 7 days.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Member registration now handles simultaneous attempts more reliably, reusing and updating an existing member when a conflict occurs.
  • Improvements
    • Company names can now include a wider range of characters.
    • Company information can be left blank.

…n1 company and concurrent creation

Company outside latin1 (full-width parentheses, Latin Extended) must be stored as sent by the IDP, and registerExternalUserById() must retry when another process creates the member between the lookups and the insert. All three fail on the current code.
…s any company name

Company was latin1, so a company name with a character outside latin1 made the write fail with 1366 in strict mode and rolled back the whole member sync. latin1 is a subset of utf8mb4, so existing values convert unchanged. The ALTER copies the table and blocks writes to Member while it runs.
…reation

When another process creates the same member between the lookups and the insert, the unique ExternalUserId or Email key rejects the insert and the message was lost. Run the sync again in a new transaction so it finds and updates that member, as the login path does.
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3d47c636-3057-48ae-bbc8-1a69b7ac524c
📥 Commits

Reviewing files that changed from the base of the PR and between 0eac852 and 878dbf0.

📒 Files selected for processing: 3
  • app/Services/Model/Imp/MemberService.php
  • database/migrations/model/Version20261021170000.php
  • tests/MemberServiceTest.php

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The pull request changes the Member.Company column to support utf8mb4 values and adds one retry to external-user registration when a unique constraint violation occurs. Tests cover company values outside Latin-1 and registration that reuses and updates an existing member after a conflict.

Changes: Company value storage

Layer / File(s) Summary
Company column and registration test
database/migrations/model/Version20261021170000.php, tests/MemberServiceTest.php
The migration changes Member.Company to nullable VARCHAR(255) with utf8mb4 and utf8mb4_general_ci. A test checks registration with company values outside Latin-1.

Registration retry

Layer / File(s) Summary
Retry registration after a unique constraint violation
app/Services/Model/Imp/MemberService.php, tests/MemberServiceTest.php
Registration by external user ID retries once after a unique constraint violation and logs the conflict. The test checks that registration reuses and updates the existing member.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: smarcet

Merge Risk: ⚪ Minimal · up to 878db

The change lets Member.Company store non-latin1 company names and retries member sync once after a concurrent creation conflict. No concrete merge-blocking defect was found. The migration rebuilds the Member table and blocks writes while it runs, so schedule it with that in mind.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes both main changes: supporting company names outside latin1 and retrying member synchronization after concurrent creation.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-652/

This page is automatically updated on each push to this PR.

This branch has not been deployed

No deployments
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