Page MenuHomePhabricator

Cannot join Toolforge: Striker throws IntegrityError while saving user
Closed, ResolvedPublic

Description

My Wikimedia account is Kavaljeet_Singh.

Developer account details:

  • SSH username: kavaljeetsingh
  • Phabricator user: Kavaljeet_Singh
  • Wikimedia Global Account (SUL): Kavaljeet Singh

IDM shows that my Wikimedia account is already linked to my Developer account.

However, when I go to https://admin.toolforge.org and click "Join now" to request Toolforge membership, I get:

"Wikimedia account 'Kavaljeet Singh' is already attached to another Developer account."

People in #wikimedia-cloud investigated and suggested opening this task. It appears that Striker may be throwing an IntegrityError while saving the user during the membership request flow.

Could someone please help investigate this issue?

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript

From striker logs: root@cloudweb1004:~# docker logs --tail 10000 striker.service 2>&1 | grep -i kavalje

ed_query)\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/cursors.py\", line 330, in _query\n    db.query(q)\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/connections.py\", line 280, in query\n    _mysql.connection.query(self, query)\nMySQLdb.IntegrityError: (1062, \"Duplicate entry 'Kavaljeet Singh' for key 'sulname'\")\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"/srv/app/striker/labsauth/views.py\", line 110, in oauth_callback\n    req.user.save()\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/contrib/auth/base_user.py\", line 76, in save\n    super().save(*args, **kwargs)\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/base.py\", line 814, in save\n    self.save_base(\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/base.py\", line 877, in save_base\n    updated = self._save_table(\n              ^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/base.py\", line 990, in _save_table\n    updated = self._do_update(\n              ^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/base.py\", line 1054, in _do_update\n    return filtered._update(values) > 0\n           ^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/query.py\", line 1231, in _update\n    return query.get_compiler(self.db).execute_sql(CURSOR)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/sql/compiler.py\", line 1984, in execute_sql\n    cursor = super().execute_sql(result_type)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/sql/compiler.py\", line 1562, in execute_sql\n    cursor.execute(sql, params)\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 67, in execute\n    return self._execute_with_wrappers(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 80, in _execute_with_wrappers\n    return executor(sql, params, many, context)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 84, in _execute\n    with self.db.wrap_database_errors:\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/utils.py\", line 91, in __exit__\n    raise dj_exc_value.with_traceback(traceback) from exc_value\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 89, in _execute\n    return self.cursor.execute(sql, params)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/mysql/base.py\", line 75, in execute\n    return self.cursor.execute(query, args)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/cursors.py\", line 179, in execute\n    res = self._query(mogrified_query)\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/cursors.py\", line 330, in _query\n    db.query(q)\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/connections.py\", line 280, in query\n    _mysql.connection.query(self, query)\ndjango.db.utils.IntegrityError: (1062, \"Duplicate entry 'Kavaljeet Singh' for key 'sulname'\")\n", "lineno": 120, "process": 1, "thread_name": "Thread-89 (process_request_thread)"}
@cee: {"@timestamp": "2026-06-21T19:28:31.687Z", "@version": "1", "message": "user.save failed", "host": "cloudweb1004", "path": "/srv/app/striker/labsauth/views.py", "tags": [], "type": "striker", "level": "ERROR", "logger_name": "striker.labsauth.views", "request_id": "020c16d4e4e8404ea495cbf82999d85a", "stack_trace": "Traceback (most recent call last):\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 89, in _execute\n    return self.cursor.execute(sql, params)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/mysql/base.py\", line 75, in execute\n    return self.cursor.execute(query, args)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/cursors.py\", line 179, in execute\n    res = self._query(mogrified_query)\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/cursors.py\", line 330, in _query\n    db.query(q)\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/connections.py\", line 280, in query\n    _mysql.connection.query(self, query)\nMySQLdb.IntegrityError: (1062, \"Duplicate entry 'Kavaljeet Singh' for key 'sulname'\")\n\nThe above exception was the direct cause of the following exception:\n\nTraceback (most recent call last):\n  File \"/srv/app/striker/labsauth/views.py\", line 110, in oauth_callback\n    req.user.save()\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/contrib/auth/base_user.py\", line 76, in save\n    super().save(*args, **kwargs)\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/base.py\", line 814, in save\n    self.save_base(\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/base.py\", line 877, in save_base\n    updated = self._save_table(\n              ^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/base.py\", line 990, in _save_table\n    updated = self._do_update(\n              ^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/base.py\", line 1054, in _do_update\n    return filtered._update(values) > 0\n           ^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/query.py\", line 1231, in _update\n    return query.get_compiler(self.db).execute_sql(CURSOR)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/sql/compiler.py\", line 1984, in execute_sql\n    cursor = super().execute_sql(result_type)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/models/sql/compiler.py\", line 1562, in execute_sql\n    cursor.execute(sql, params)\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 67, in execute\n    return self._execute_with_wrappers(\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 80, in _execute_with_wrappers\n    return executor(sql, params, many, context)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 84, in _execute\n    with self.db.wrap_database_errors:\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/utils.py\", line 91, in __exit__\n    raise dj_exc_value.with_traceback(traceback) from exc_value\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/utils.py\", line 89, in _execute\n    return self.cursor.execute(sql, params)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/django/db/backends/mysql/base.py\", line 75, in execute\n    return self.cursor.execute(query, args)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/cursors.py\", line 179, in execute\n    res = self._query(mogrified_query)\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/cursors.py\", line 330, in _query\n    db.query(q)\n  File \"/opt/lib/poetry/striker-2uZo5AhP-py3.11/lib/python3.11/site-packages/MySQLdb/connections.py\", line 280, in query\n    _mysql.connection.query(self, query)\ndjango.db.utils.IntegrityError: (1062, \"Duplicate entry 'Kavaljeet Singh' for key 'sulname'\")\n", "lineno": 120, "process": 1, "thread_name": "Thread-30 (process_request_thread)"}

specifically Duplicate entry 'Kavaljeet Singh' for key 'sulname'\

I'm not seeing a duplicate:

striker_admin@m5-master.eqiad.wmnet(striker)> select id from labsauth_labsuser where sulname = 'Kavaljeet Singh'\G
*************************** 1. row ***************************
id: 5374
1 row in set (0.001 sec)

Did someone clean this up or is there a weird interaction happening that I don't understand yet?

I looked at, and reported, logs as part of clinic duty though did not touch the db FWIW

I'm not seeing a duplicate:
[cut]
Did someone clean this up or is there a weird interaction happening that I don't understand yet?

The row you are seeing is the duplicate. @Kavaljeet_Singh has two developer accounts, Kavaljeet Singh (uid=kavaljeetsingh), and Kavaljeet_Singh (uid=kavaljeetsinghdev). The former is linked via Bitu to the Kavaljeet Singh SUL account, but the linkage in Striker is done with the latter developer account.

Thanks @taavi. I'm not sure why I didn't figure that out yet. :)

The https://ldap.toolforge.org/user/kavaljeetsinghdev Developer account is currently connected to the https://en.wikipedia.org/wiki/User:Kavaljeet_Singh SUL account in the internal Striker (https://toolsadmin.wikimedia.org/ database).

The https://ldap.toolforge.org/user/kavaljeetsingh account is linked to the same SUL account via the LDAP entry managed through https://idm.wikimedia.org.

I think I now understand that the active is issue is that @Kavaljeet_Singh is attempting to setup the uid=kavaljeetsingh account as a Toolforge member. To make that possible we need to drop the association of the User:Kavaljeet_Singh account from the Striker database.

I'm also wondering if the uid=kavaljeetsinghdev Developer account that the duplicate association exits with now should be disabled? Is there a specific reason you want to have two Developer accounts @Kavaljeet_Singh?

Thanks for investigating and identifying the issue.

I do not intentionally need two Developer accounts. I only want to use the kavaljeetsingh Developer account, which is the account currently linked through IDM and that I use with Gerrit and Phabricator.

If the kavaljeetsinghdev Developer account is no longer needed, I am happy for it to be disassociated or disabled if that is the correct way to resolve this issue.

Thank you for your help.

bd808 claimed this task.

Per T429759#12098241 I took these actions to unblock things:

@Kavaljeet_Singh, you should be able to use https://toolsadmin.wikimedia.org to connect your Developer account to your SUL and Phabricator accounts now and finish applying for Toolforge membership. If you get the same error message please do reopen this task.

Argh. I missed clearing the sulid value from the legacy account's labsauth_labsuser record.

striker_admin@m5-master.eqiad.wmnet(striker)> select id, sulid, sulname, sulemail, oauthtoken, oauthsecret from labsauth_labsuser where shellname = 'kavaljeetsinghdev'\G
*************************** 1. row ***************************
         id: 5374
      sulid: 78889671
    sulname: NULL
   sulemail:
 oauthtoken: NULL
oauthsecret: NULL
1 row in set (0.001 sec)

striker_admin@m5-master.eqiad.wmnet(striker)> update labsauth_labsuser set sulid=null, sulname=null, sulemail=null, oauthtoken=null, oauthsecret=null where id=5374;
Query OK, 1 row affected (0.002 sec)
Rows matched: 1  Changed: 1  Warnings: 0

striker_admin@m5-master.eqiad.wmnet(striker)> select id, sulid, sulname, sulemail, oauthtoken, oauthsecret from labsauth_labsuser where shellname = 'kavaljeetsinghdev'\G
*************************** 1. row ***************************
         id: 5374
      sulid: NULL
    sulname: NULL
   sulemail: NULL
 oauthtoken: NULL
oauthsecret: NULL
1 row in set (0.001 sec)

Sorry for the false start @Kavaljeet_Singh. Please try again and let me know if it works or not now.

It works now. I was able to complete the Toolforge membership process.

Thank you for your help and for fixing the issue.