Skip to content

Share one Keycloak admin client across parallel team syncs - #4

Merged
jontyms merged 1 commit into
mainfrom
fix/keycloak-shared-client
Sep 12, 2026
Merged

jontyms merged 1 commit into
mainfrom
fix/keycloak-shared-client

Conversation

@jontyms

@jontyms jontyms commented Sep 12, 2026

Copy link
Copy Markdown
Member

Problem

In prod, one team's sync intermittently fails while the team syncing alongside it — same credentials, same second — succeeds:

Processing Team: testing
Processing Team: synced
...
keycloak.exceptions.KeycloakPostError: 400: b'{"error":"invalid_grant","error_description":"Invalid user credentials"}'
ValueError: KEYCLOAK group returned empty: testing
...
Adding ethanperezrivera24 to synced
Processing Team Successful: synced

sync_all_teams runs teams in a ThreadPoolExecutor(max_workers=10), and directory_group_members builds a fresh DirectoryClient()KeycloakAdmin per call. So each run fires N concurrent password grants for the same service account, and Keycloak rejects some of them.

Fix

githubapp/keycloak.py:

  • Build one KeycloakAdmin per process (lazily, under a lock) and hand it to every Keycloak() instance.
  • Subclass KeycloakOpenIDConnection so refresh_token() is serialised: a thread that waits on the lock re-checks expires_at and skips the refresh if another thread already did it.

python-keycloak already handles refreshing the shared token on expiry, so the only behavioural change is one login session instead of one per team per run.

Verification

  • ruff check / ruff format --check clean.
  • Scratch test with KeycloakOpenID.token mocked: 10 threads each constructing Keycloak() and forcing a refresh → 1 client instance, 1 password grant (previously 10).

🤖 Generated with Claude Code

https://claude.ai/code/session_01UGRXmBT8HSsodKUkYXRAZK

Every team sync built its own KeycloakAdmin, and teams are synced in a
thread pool, so each run fired several concurrent password grants for the
same service account. Keycloak intermittently rejects one of them:

    keycloak.exceptions.KeycloakPostError: 400: b'{"error":"invalid_grant",
    "error_description":"Invalid user credentials"}'

and that team's sync fails with "KEYCLOAK group returned empty" while the
team next to it, using the same credentials, succeeds.

Build the client once per process and reuse it; token (re)acquisition is
serialised behind a lock so threads never race each other to the token
endpoint. python-keycloak already refreshes the shared token on expiry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGRXmBT8HSsodKUkYXRAZK
@jontyms
jontyms merged commit 14c6e18 into main Sep 12, 2026
3 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