String prefix "bot-" as an ID convention is fragile — nothing enforces it in the domain model. This will silently fail (not notify the bot) if the prefix ever changes or if a regular user's ID happens to start with "bot-". Introduce a typed BotId/PlayerId sum type or a boolean flag on PlayerInfo instead.
A @PermitAll endpoint that creates a game and triggers bot activity has no rate-limiting or identity attached to the player. Any unauthenticated caller can drive unlimited bot load. Either require authentication (@RolesAllowed("**")) or add explicit rate-limiting before exposing this in production.
Swallowing a rejected RejectedExecutionException silently drops moves. At minimum log the rejection so it shows up in monitoring.
newSingleThreadExecutor() uses an unbounded queue. Under a slow game handler, tasks will queue indefinitely. Use new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(1000), r -> ..., new DiscardOldestPolicy()) to bound memory.
shutdownNow() interrupts running tasks but doesn't wait for them to finish. Tasks already submitted to the executor may still be mid-handleC2sMessage when the game is considered gone. Consider awaitTermination(200, MILLISECONDS) after shutdownNow() to give in-flight tasks a chance to complete cleanly.
Building JSON by string interpolation is fragile — any gameId or botId containing " or \ will produce invalid JSON and silently break the downstream OfficialBotService. Use objectMapper.writeValueAsString(Map(...)) or a structured DTO.
Missing nullable = false. A just-constructed OfficialBotAccount starts with token = uninitialized, and AccountService does two separate persist() calls to work around this. Add nullable = false and generate the token before the first persist to collapse it to one round-trip and make the constraint explicit at the DB level.
bot.token can be null if the Hibernate entity was just persisted (the token is not nullable = false on OfficialBotAccount). Wrap with
This is a heap dump filename with a specific PID, meaning a heap dump was taken during development. Confirm the dump file itself is not tracked in the repository (git ls-files
The entire file operates on a daemon thread with Thread.sleep(5000) reconnect. This bypasses Quarkus CDI lifecycle: it won't receive the shutdown signal, won't drain in-flight work on @PreDestroy, and the flat 5-second backoff will hammer the server if the tournament endpoint is persistently unavailable. Use @Scheduled or a Vert.x timer with exponential backoff, and track the thread reference so it can be interrupted on graceful shutdown.
Non-expiring tokens are a security liability. If a bot token is leaked there is no recovery short of deleting the account. At minimum use a multi-year expiry, and surface a token-rotation endpoint so operators have a recovery path.
Same two-persist pattern. The root cause is that bot.id is not available before the first persist(). Either use a pre-assigned UUID (@Id with UUID.randomUUID() before persist) or derive the token from a field that doesn't require a DB round-trip. Two flushes per creation is a correctness risk under failure: if the second persist fails, the entity exists without a valid token.
The token is set to a random UUID, persisted, then immediately overwritten with the real JWT and persisted again. This is two DB writes and leaves a window where the entity carries a meaningless token. Generate the ID first, build the JWT, set it once, then persist.
The PR description acknowledges a null white player for bye rounds. Per project convention, this must be Option[PlayerId] (or a Bye case in a sum type), not a nullable field. A null propagating through the SwissPairingService and TournamentResource will eventually reach a serializer or comparison that throws rather than producing a well-formed API response.