Skip to content

Offload discord postings to background worker #2003

Open
zunath wants to merge 8 commits intomasterfrom
codex/replace-bug-report-system-with-github
Open

Offload discord postings to background worker #2003
zunath wants to merge 8 commits intomasterfrom
codex/replace-bug-report-system-with-github

Conversation

@zunath
Copy link
Copy Markdown
Owner

@zunath zunath commented May 7, 2026

Summary by CodeRabbit

  • Updates

    • Bug reporting now uses GitHub-based settings and queues reports instead of sending webhooks synchronously.
    • In-app broadcasts and DMs now enqueue background deliveries rather than sending immediately.
  • New Features

    • Added a background worker service to create GitHub issues and deliver Discord webhooks asynchronously.
    • Docker Compose updated to run the background service.
  • Documentation

    • Deployment guide updated to cover external integration and GitHub bug reporting.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Redis-stream background jobs with server enqueue API, a background worker project (handlers for Discord and GitHub), failure/dead-letter handling, settings and logging, and updates game viewmodels, Docker/compose, env vars, and deployment docs.

Changes

Background job worker + bug reporting

Layer / File(s) Summary
Deployment & configuration
SWLOR.Game.Server/Docker/swlor.env, SWLOR.Game.Server/Docker/docker-compose.yml, SWLOR.Game.Server/Readmes/Deployment.md, SWLOR.Game.Server.sln, SWLOR.Game.Server/SWLOR.Game.Server.csproj
Replaces SWLOR_BUG_WEBHOOK_URL with GitHub repo/token variables, preserves Discord shout/holonet vars, adds background-services service and grafana volume, updates README text, and adds project to solution; updates Discord.Net package reference.
Payload models & DB wrapper
SWLOR.Game.Server/Service/BackgroundJobService/*, SWLOR.Game.Server/Service/DB.cs
Adds server-side GitHubIssuePayload and DiscordWebhookPayload classes, BackgroundJobType enum, and DB.StreamAdd/StreamAddAsync wrappers for Redis stream appends.
Server enqueue API
SWLOR.Game.Server/Service/BackgroundJob.cs
Adds EnqueueGitHubIssue and EnqueueDiscordWebhook helpers that serialize payloads and call DB.StreamAdd into the background-job stream.
ViewModels & call sites
SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs, .../HoloNetViewModel.cs, Feature/ChatCommandDefinition/DMChatCommand.cs
Removes inline Discord webhook sends; builds UTC metadata and thread-name helpers; validates config and calls BackgroundJob.EnqueueDiscordWebhook(...); enqueue failures are reported to players.
Background service project & settings
SWLOR.BackgroundServices/*
New .NET 8 executable project with Dockerfile; Program reads env settings, creates logger/HttpClient, registers handlers, and runs the background worker.
Infrastructure: logger & HTTP helper
SWLOR.BackgroundServices/Infrastructure/IAppLogger.cs, .../ConsoleAppLogger.cs, .../JsonHttpContent.cs
Adds IAppLogger and ConsoleAppLogger plus JsonHttpContent.Create helper used by handlers.
Queue constants & job parsing
SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobQueueNames.cs, .../BackgroundJobTypes.cs, .../BackgroundJob.cs
Defines stream/consumer group names and job type identifiers and adds BackgroundJob parsing/factory for Redis stream entries.
Processor, failure handling, and worker loop
SWLOR.BackgroundServices/BackgroundJobs/*
Implements BackgroundJobProcessor, BackgroundJobFailureHandler (requeueing and dead-letter via Lua scripts, truncation), and BackgroundJobWorker with connection/retry, consumer-group ensure, and pending/new read loop.
Background job handlers
SWLOR.BackgroundServices/BackgroundJobs/Handlers/*.cs, SWLOR.BackgroundServices/BackgroundJobs/IBackgroundJobHandler.cs
Adds DiscordWebhookJobHandler (thread-name handling, 429/backoff, retries) and GitHubIssueJobHandler (authenticated issues POST, optional Codex prompt) implementing IBackgroundJobHandler.
BackgroundServices models
SWLOR.BackgroundServices/BackgroundJobs/Models/*
Adds handler-side DiscordWebhookPayload and GitHubIssuePayload JSON models used by handlers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble keys and watch streams flow,
I queue the posts where webhooks go.
From game to Redis, tiny hops of code,
Issues and threads in a tidy load.
A carrot-tapped enqueue — onward we go!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Offload discord postings to background worker' partially describes a key aspect of the changeset (delegating Discord webhook work to a background job queue), but does not capture the primary objective stated in the PR objectives: replacing Discord webhook-based bug reporting with GitHub Issues. Consider revising the title to better reflect the main change: e.g., 'Replace Discord webhook bug reports with GitHub Issues and background job queue' or 'Implement GitHub Issues for bug reports and async background job processing'.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/replace-bug-report-system-with-github

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs`:
- Line 65: Change the timestamp format used when building the dateReported
string in BugReportViewModel to use the 24-hour token and invariant culture:
replace the current format "yyyy-MM-dd hh:mm:ss" with "yyyy-MM-dd HH:mm:ss" and
pass CultureInfo.InvariantCulture into the ToString call where dateReported is
assigned so the UTC timestamp is unambiguous and culture-stable.
- Around line 87-138: SubmitBugReportToGitHub creates a new HttpClient per call
which can exhaust sockets; replace that with a shared static HttpClient
initialized once using a SocketsHttpHandler configured with
PooledConnectionLifetime (e.g. 2 minutes) and any desirable timeout, and stop
adding Authorization to DefaultRequestHeaders—instead set the Bearer token on
the individual HttpRequestMessage used for PostAsync so token rotation works;
update SubmitBugReportToGitHub to build an HttpRequestMessage (with per-request
Authorization, User-Agent and Accept headers) and call
staticHttpClient.SendAsync(request).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c3af811f-270c-4dcd-b11a-ccbd78031337

📥 Commits

Reviewing files that changed from the base of the PR and between 9e7ea79 and cc5d18d.

📒 Files selected for processing (3)
  • SWLOR.Game.Server/Docker/swlor.env
  • SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs
  • SWLOR.Game.Server/Readmes/Deployment.md

Comment thread SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs Outdated
Comment thread SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs Outdated
@zunath
Copy link
Copy Markdown
Owner Author

zunath commented May 7, 2026

@codex review all comments and make fixes

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@zunath
Copy link
Copy Markdown
Owner Author

zunath commented May 7, 2026

@codex fix the issues raised by CodeRabbit

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • Reviewed the actionable CodeRabbit feedback and implemented both still-valid fixes in BugReportViewModel.
  • Updated dateReported formatting to 24-hour UTC with invariant culture (yyyy-MM-dd HH:mm:ss, CultureInfo.InvariantCulture) to remove AM/PM ambiguity and keep format stable across locales.
  • Replaced per-call HttpClient creation with a shared static client using SocketsHttpHandler and PooledConnectionLifetime, plus a 15-second timeout, to address socket reuse/lifetime concerns.
  • Switched GitHub auth to per-request HttpRequestMessage headers instead of DefaultRequestHeaders.Authorization, and now send requests through the shared static client.
  • Committed the changes on the current branch and created a follow-up PR record via make_pr titled “Follow-up: resolve CodeRabbit bug report submission issues”.

Testing

  • find / -name AGENTS.md 2>/dev/null | head -50
  • git -C /workspace/SWLOR_NWN diff -- SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs
  • nl -ba /workspace/SWLOR_NWN/SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs | sed -n '1,220p'
  • git -C /workspace/SWLOR_NWN add SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs && git -C /workspace/SWLOR_NWN commit -m "Address CodeRabbit bug report view model feedback"
  • web.search_query: "Microsoft HttpClient guidelines static instance pooledconnectionlifetime" (used to verify current guidance before final response).

View task →

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
SWLOR.BackgroundServices/Configuration/BackgroundServiceSettings.cs (1)

63-69: 💤 Low value

GetInt silently falls back to the default when 0 is explicitly configured

parsed > 0 treats zero as invalid and returns the default. This means operators can't distinguish between "env var not set" and "env var set to 0" — for example, setting SWLOR_BACKGROUND_EMPTY_READ_DELAY_SECONDS=0 would silently use 1 second. At minimum, consider logging a warning when a non-positive value is encountered.

💡 Proposed improvement
 private static int GetInt(string name, int defaultValue)
 {
     var value = Environment.GetEnvironmentVariable(name);
-    return int.TryParse(value, out var parsed) && parsed > 0
-        ? parsed
-        : defaultValue;
+    if (string.IsNullOrWhiteSpace(value))
+        return defaultValue;
+    if (int.TryParse(value, out var parsed) && parsed > 0)
+        return parsed;
+    // Non-positive or unparseable value — fall back to default silently.
+    // Consider logging a warning here if a logger is accessible.
+    return defaultValue;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.BackgroundServices/Configuration/BackgroundServiceSettings.cs` around
lines 63 - 69, GetInt currently rejects zero because it uses parsed > 0; change
it to accept zero by returning parsed when int.TryParse succeeds and parsed >=
0, and only fall back to default when parse fails or parsed < 0. Also emit a
warning (use your ILogger/processLogger) when a negative value is provided so
operators know the env var was read but invalid (e.g.
SWLOR_BACKGROUND_EMPTY_READ_DELAY_SECONDS set to -1). Update the GetInt method
implementation and add the warning/log line inside that method when parsed < 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobFailureHandler.cs`:
- Around line 43-55: MoveToDeadLetterAsync has the same non-atomic pattern as
HandleFailureAsync: it XADDs the dead-letter entry via StreamAddAsync then
separately calls AcknowledgeAsync, risking duplicate dead-letter entries if ACK
fails; replace this with an atomic Redis Lua script that performs XADD to
BackgroundJobQueueNames.DeadLetterStreamName and XACK for the original entry.Id
in one EVAL call (use the Redis scripting/ScriptEvaluateAsync entry-point used
elsewhere or add a helper like ExecuteAtomicallyAsync), then remove the separate
AcknowledgeAsync call from MoveToDeadLetterAsync; if you cannot change runtime
behavior now, add explicit documentation on MoveToDeadLetterAsync and
BackgroundJobQueueNames that the dead-letter stream is at-most-once and
consumers must dedupe/handle duplicate entries.
- Around line 28-40: The retry enqueue currently omits the stream max length and
performs XADD then AcknowledgeAsync separately, which can overflow the stream
and cause duplicate requeues if AcknowledgeAsync fails; modify the
StreamAddAsync call in BackgroundJobFailureHandler to pass the same max
length/approximate options used on initial enqueue (e.g., MaxLenApprox = 10000)
and replace the separate StreamAddAsync + AcknowledgeAsync pair with a single
Redis Lua script (EVAL) that performs XADD with MAXLEN (or ~MAXLEN) and XACK
atomically, using the same field set (type, payload, attempt, createdUtc,
lastError) and the job.Id for XACK; keep AcknowledgeAsync logic but change
BackgroundJobFailureHandler to call the new Lua-based helper so retry
enqueue+ack are atomic and bounded.

In `@SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobProcessor.cs`:
- Around line 38-43: The call sequence awaiting handler.HandleAsync(...)
immediately followed by database.StreamAcknowledgeAsync(...) is non-atomic: if
StreamAcknowledgeAsync fails the job will be re-delivered and the handler (e.g.,
in HandleAsync implementations) may run twice and then be routed through
HandleFailureAsync/BackgroundJobFailureHandler incorrectly; add a clear code
comment in BackgroundJobProcessor (near the HandleAsync → StreamAcknowledgeAsync
block) stating the system provides at-least-once delivery and that handler
implementations must be idempotent (or perform their own deduplication, e.g.,
GitHub issue title existence check), and optionally suggest patterns (retry ack
logic, persistent outbox, or idempotency keys) for future refactors.
- Around line 33-36: The code currently throws InvalidOperationException when
_handlers.TryGetValue(backgroundJob.Type, out var handler) fails, which funnels
into HandleFailureAsync and causes pointless retries; instead, immediately
dead-letter the job using the same dead-letter logic used in the TryCreate
failure path (do not throw or call HandleFailureAsync). Replace the throw with a
call to the existing dead-letter routine (use the same method/logic invoked when
TryCreate fails) passing the backgroundJob and an explanatory message about
unknown job type so the entry is moved to the dead-letter stream immediately.

In `@SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobWorker.cs`:
- Line 25: The code logs the raw Redis connection string via
_logger.Info($"Connecting to Redis at {_settings.RedisConnection}...") which may
expose credentials; update the BackgroundJobWorker startup log to redact
passwords by parsing _settings.RedisConnection (look for "password=" or "pwd="
key/value or a URI userinfo) and replace the value with a placeholder like
"[REDACTED]" before passing it to _logger.Info, or log only the host:port
portion; ensure you modify the logging call that references
_settings.RedisConnection so no plaintext password is emitted.

In
`@SWLOR.BackgroundServices/BackgroundJobs/Handlers/DiscordWebhookJobHandler.cs`:
- Around line 74-87: ResolveThreadName currently returns job.Title when Title is
non-empty even if CreateThread is false, causing threads to be auto-created;
update ResolveThreadName (in DiscordWebhookJobHandler) so it only returns
job.Title when job.CreateThread is true and Title is non-empty (i.e., change the
condition to require job.CreateThread && !string.IsNullOrWhiteSpace(job.Title)),
otherwise return string.Empty unless job.ThreadName is explicitly provided; this
ensures the handler's check that only creates a thread when CreateThread is true
behaves correctly.

In `@SWLOR.BackgroundServices/Dockerfile`:
- Around line 10-13: The runtime stage runs the app as root; create a non-root
user and ensure app files are owned by that user, then switch to it before
ENTRYPOINT. In the Dockerfile's runtime stage (the lines with WORKDIR /app, COPY
--from=build /app/publish ., and ENTRYPOINT ["dotnet",
"SWLOR.BackgroundServices.dll"]), add commands to create a user/group (e.g.,
appuser), chown the /app directory to that user, and set USER to that account so
the container runs the dotnet process non-root. Ensure the created UID/GID are
consistent and that ownership is applied to the copied publish files.

In `@SWLOR.BackgroundServices/Program.cs`:
- Line 22: The code calls worker.RunAsync with CancellationToken.None which
ignores SIGTERM/SIGINT and prevents graceful shutdown; replace
CancellationToken.None by a cancellation token that is signalled on process stop
(e.g., create a CancellationTokenSource and cancel it from
Console.CancelKeyPress and AppDomain.ProcessExit, or use the host's
IHostApplicationLifetime.ApplicationStopping token) and pass that token into
worker.RunAsync; locate the call to worker.RunAsync in Program.cs and change it
to use the new token (e.g., cts.Token or appLifetime.ApplicationStopping) so the
worker can exit cleanly and close Redis connections on container stop.

In `@SWLOR.Game.Server/Feature/ChatCommandDefinition/DMChatCommand.cs`:
- Around line 954-957: The call to BackgroundJob.EnqueueDiscordWebhook in
DMChatCommand currently ignores failures; capture its result and handle errors
explicitly: call BackgroundJob.EnqueueDiscordWebhook(url, authorName, message,
15105570) into a variable, check for a false/failed result or catch exceptions
around the call, and on failure emit a Serilog error (include url, authorName
and message for context) and optionally notify the DM sender or retry/fallback
as appropriate; ensure you update only the DMChatCommand code path surrounding
the enqueue so failures are not silently dropped.

In `@SWLOR.Game.Server/Service/BackgroundJob.cs`:
- Around line 51-73: Enqueue<TPayload> currently calls the blocking DB.StreamAdd
inside the NWN game thread; change it to call an asynchronous variant
(DB.StreamAddAsync) and fire-and-forget it from a background Task or dedicated
thread so the game thread never blocks, or if no async API exists, offload the
blocking call to Task.Run with a bounded timeout on the Redis client and a
cancellation token; update the Enqueue method (and any callers) to start the
background operation and immediately return, and enhance the Log.Write error
message to include the BackgroundJobType (type) plus any player/context info
available so failures are actionable (references: Enqueue<TPayload>,
DB.StreamAdd/StreamAddAsync, MaxStreamLength, useApproximateMaxLength,
Log.Write).

---

Nitpick comments:
In `@SWLOR.BackgroundServices/Configuration/BackgroundServiceSettings.cs`:
- Around line 63-69: GetInt currently rejects zero because it uses parsed > 0;
change it to accept zero by returning parsed when int.TryParse succeeds and
parsed >= 0, and only fall back to default when parse fails or parsed < 0. Also
emit a warning (use your ILogger/processLogger) when a negative value is
provided so operators know the env var was read but invalid (e.g.
SWLOR_BACKGROUND_EMPTY_READ_DELAY_SECONDS set to -1). Update the GetInt method
implementation and add the warning/log line inside that method when parsed < 0.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f3f72984-e94d-440d-974d-d32ea5e18124

📥 Commits

Reviewing files that changed from the base of the PR and between 9452678 and 18cca6b.

📒 Files selected for processing (30)
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJob.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobFailureHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobProcessor.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobQueueNames.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobTypes.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobWorker.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Handlers/DiscordWebhookJobHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Handlers/GitHubIssueJobHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/IBackgroundJobHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Models/DiscordWebhookPayload.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Models/GitHubIssuePayload.cs
  • SWLOR.BackgroundServices/Configuration/BackgroundServiceSettings.cs
  • SWLOR.BackgroundServices/Dockerfile
  • SWLOR.BackgroundServices/Infrastructure/ConsoleAppLogger.cs
  • SWLOR.BackgroundServices/Infrastructure/IAppLogger.cs
  • SWLOR.BackgroundServices/Infrastructure/JsonHttpContent.cs
  • SWLOR.BackgroundServices/Program.cs
  • SWLOR.BackgroundServices/SWLOR.BackgroundServices.csproj
  • SWLOR.Game.Server.sln
  • SWLOR.Game.Server/Docker/docker-compose.yml
  • SWLOR.Game.Server/Docker/swlor.env
  • SWLOR.Game.Server/Feature/ChatCommandDefinition/DMChatCommand.cs
  • SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs
  • SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/HoloNetViewModel.cs
  • SWLOR.Game.Server/SWLOR.Game.Server.csproj
  • SWLOR.Game.Server/Service/BackgroundJob.cs
  • SWLOR.Game.Server/Service/BackgroundJobService/BackgroundJobType.cs
  • SWLOR.Game.Server/Service/BackgroundJobService/DiscordWebhookPayload.cs
  • SWLOR.Game.Server/Service/BackgroundJobService/GitHubIssuePayload.cs
  • SWLOR.Game.Server/Service/DB.cs
💤 Files with no reviewable changes (1)
  • SWLOR.Game.Server/SWLOR.Game.Server.csproj
✅ Files skipped from review due to trivial changes (9)
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobTypes.cs
  • SWLOR.BackgroundServices/BackgroundJobs/IBackgroundJobHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobQueueNames.cs
  • SWLOR.Game.Server/Service/BackgroundJobService/BackgroundJobType.cs
  • SWLOR.BackgroundServices/Infrastructure/JsonHttpContent.cs
  • SWLOR.Game.Server/Service/DB.cs
  • SWLOR.BackgroundServices/SWLOR.BackgroundServices.csproj
  • SWLOR.Game.Server/Service/BackgroundJobService/GitHubIssuePayload.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Models/GitHubIssuePayload.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • SWLOR.Game.Server/Docker/swlor.env

Comment thread SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobFailureHandler.cs Outdated
Comment thread SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobProcessor.cs Outdated
Comment thread SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobProcessor.cs
Comment thread SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobWorker.cs Outdated
Comment thread SWLOR.BackgroundServices/Dockerfile
Comment thread SWLOR.BackgroundServices/Program.cs Outdated
Comment thread SWLOR.Game.Server/Feature/ChatCommandDefinition/DMChatCommand.cs
Comment thread SWLOR.Game.Server/Service/BackgroundJob.cs
@zunath
Copy link
Copy Markdown
Owner Author

zunath commented May 8, 2026

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 949b6319ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@zunath
Copy link
Copy Markdown
Owner Author

zunath commented May 8, 2026

@coderabbitai review now that rate limits are over

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

@zunath Sure! I'll kick off a fresh full review of PR #2003 right away.

✅ Actions performed

Full review triggered.

@zunath
Copy link
Copy Markdown
Owner Author

zunath commented May 8, 2026

@coderabbitai review now that rate limits are over

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

@zunath Sure! Triggering a full review of PR #2003 now.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs (1)

46-75: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bug reports are still routed to Discord, not GitHub Issues.

The submit path currently depends on SWLOR_BUG_DISCORD_WEBHOOK_URL and EnqueueDiscordWebhook(...), so it does not implement the PR’s stated GitHub-issue migration behavior.

💡 Suggested direction
- var discordWebhookUrl = Environment.GetEnvironmentVariable("SWLOR_BUG_DISCORD_WEBHOOK_URL");
- if (string.IsNullOrWhiteSpace(discordWebhookUrl)) { ... }
+ var githubRepository = Environment.GetEnvironmentVariable("SWLOR_BUG_GITHUB_REPOSITORY");
+ if (string.IsNullOrWhiteSpace(githubRepository)) { ... }

- if (!SubmitBugReportToDiscord(discordWebhookUrl, ...)) { ... }
+ if (!SubmitBugReportToGitHub(githubRepository, ...)) { ... }

- return BackgroundJob.EnqueueDiscordWebhook(...);
+ return BackgroundJob.EnqueueGitHubIssue(githubRepository, title, body);

Also applies to: 84-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs`
around lines 46 - 75, The code currently always sends bug reports via the
Discord webhook check and SubmitBugReportToDiscord; update the flow to prefer
creating a GitHub issue when the new GitHub submission path is available:
add/check a GitHub target environment/config (e.g., a GitHub repo or token env)
and call a new SubmitBugReportToGitHubIssue(...) method (or refactor existing
submission code) using the same metadata (authorName, areaName, areaTag,
areaResref, positionGroup, dateReported, playerId, message); if GitHub
submission fails or the GitHub config is absent, then fall back to the existing
SubmitBugReportToDiscord(...) path, preserving the error messages
(SendMessageToPC) and rate-limiting behavior (nextReportAllowed). Do the same
replacement for the secondary block referenced (lines ~84-121) so both code
paths use GitHub first and Discord as fallback, and ensure any
EnqueueDiscordWebhook(...) usages are only invoked when Discord is the chosen
fallback.
🧹 Nitpick comments (3)
SWLOR.BackgroundServices/Infrastructure/IAppLogger.cs (1)

3-8: ⚡ Quick win

Consider replacing IAppLogger with Serilog.ILogger or Microsoft.Extensions.Logging.ILogger.

Rolling a custom logger interface duplicates what both Serilog and MEL already provide (including structured logging, log levels, and a rich ecosystem of sinks). The codebase's coding guideline mandates Serilog; wiring the standard Serilog.ILogger through the worker program would satisfy that requirement without the bespoke abstraction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.BackgroundServices/Infrastructure/IAppLogger.cs` around lines 3 - 8,
The custom IAppLogger interface (IAppLogger with Info, Error(string),
Error(string, Exception)) duplicates existing logging abstractions; replace
usages and registration of IAppLogger with the standard Serilog.ILogger (or
Microsoft.Extensions.Logging.ILogger<T>) by removing IAppLogger, updating
consumers to accept/consume Serilog.ILogger (or ILogger<T>), and wiring the
chosen logger in the host/worker registration so dependency injection provides
the real Serilog logger instance; ensure calls currently targeting Info and
Error map to the appropriate ILogger methods (e.g., Information, Error) and
update any exception overload usage accordingly.
SWLOR.Game.Server/Docker/docker-compose.yml (1)

38-44: Consider adding a Redis readiness health check to avoid a startup race.

depends_on: redis only guarantees the Redis container has started, not that it is ready to accept connections. The background worker may attempt to connect before Redis is listening and fail. A health-check condition makes this robust:

⚙️ Suggested health-check dependency
 background-services:
     hostname: background-services
     image: zunath/swlor-background-worker:latest
     env_file: ${PWD-.}/swlor.env
     depends_on:
-        - redis
+        redis:
+            condition: service_healthy
     restart: unless-stopped

Add a health check to the redis service:

 redis:
     hostname: redis
     image: redislabs/redismod:latest
     volumes:
       - ${PWD-.}/redis:/data
     restart: unless-stopped
+    healthcheck:
+        test: ["CMD", "redis-cli", "ping"]
+        interval: 5s
+        timeout: 3s
+        retries: 5
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.Game.Server/Docker/docker-compose.yml` around lines 38 - 44, The redis
service needs a healthcheck and the background-services should depend on redis
being healthy; add a healthcheck block to the redis service (use a command like
"redis-cli PING" and set interval, timeout and retries) and update
background-services' depends_on to wait for the redis service_healthy condition
so the background worker (background-services) only starts after Redis is ready
to accept connections.
SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobProcessor.cs (1)

24-27: ⚡ Quick win

Add a log entry when malformed jobs are dead-lettered.

Line 26 dead-letters parse failures, but that path is currently silent. Add a direct error log with entry.Id and error before returning so malformed payloads are traceable in production.

Proposed change
 if (!BackgroundJob.TryCreate(entry, out var job, out var error))
 {
     await _failureHandler.MoveToDeadLetterAsync(database, entry, error);
+    _logger.Error($"Background job {entry.Id} dead-lettered due to malformed payload: {error}");
     return;
 }

As per coding guidelines, "Use comprehensive logging via Serilog framework throughout the codebase".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobProcessor.cs` around
lines 24 - 27, Add a Serilog error log right before returning when
BackgroundJob.TryCreate fails: log the entry.Id and the parsed error so
malformed payloads are traceable in production. Update the failure path in
BackgroundJobProcessor where BackgroundJob.TryCreate(entry, out var job, out var
error) returns false to call the logger (include entry.Id and error) immediately
before invoking _failureHandler.MoveToDeadLetterAsync(database, entry, error) or
at least before the return. Ensure the log uses the same logger instance used in
the class and formats the message with structured properties (entry.Id and
error).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SWLOR.BackgroundServices/BackgroundJobs/BackgroundJob.cs`:
- Around line 40-44: The ParseAttempt method currently returns whatever
int.TryParse yields, which may be negative; update ParseAttempt to clamp any
parsed value to a non-negative number (zero or greater) before returning—e.g.,
after parsing in ParseAttempt(string? value) ensure the returned attempt is
Math.Max(0, attempt) so negative inputs become 0 and retry accounting remains
correct.

In `@SWLOR.BackgroundServices/Infrastructure/ConsoleAppLogger.cs`:
- Around line 3-18: ConsoleAppLogger currently writes directly to the console in
Info and Error methods; replace those calls with Serilog so logs are consistent
and structured: add the Serilog NuGet and update the ConsoleAppLogger class to
call Serilog.Log.Information for Info(string message), Serilog.Log.Error for
Error(string message) and Serilog.Log.Error(exception, message) for the
Error(string message, Exception exception) overload, removing direct
Console/Console.Error usage and relying on Serilog configuration (timestamp,
sinks, enrichment) like other services that use the Log service.

In `@SWLOR.Game.Server/Service/BackgroundJob.cs`:
- Around line 64-67: The method currently fire-and-forgets the Redis write by
calling Task.Run(() => EnqueueAsync(type, entries, context)) and returns true
immediately, causing callers to get false success; change the public enqueue
methods to await the background enqueue operation and propagate its
success/failure: call and await EnqueueAsync(type, entries, context) (or change
the method signature to return Task<bool> if not already) and return the actual
boolean result from EnqueueAsync (or catch exceptions and return false after
logging), ensuring the caller only receives true when the Redis enqueue
succeeded; locate usages of Enqueue* and EnqueueAsync in BackgroundJob.cs to
update their signatures and callers accordingly.
- Around line 91-99: BuildLogContext currently includes free-form fields (Title,
Description, AuthorName) that may contain sensitive user-supplied text; update
the switch in BuildLogContext to stop logging truncated message content and only
emit non-sensitive identifiers. For DiscordWebhookPayload return a context
string containing only threadId and createThread (e.g., threadId and
CreateThread flag) and omit AuthorName/Title/Description, and for
GitHubIssuePayload return only repository (omit issue Title); adjust the
returned strings in the BuildLogContext method to remove Truncate(...) usage for
free-form fields and keep only safe identifiers.

In `@SWLOR.Game.Server/Service/BackgroundJobService/GitHubIssuePayload.cs`:
- Around line 8-14: The GitHubIssuePayload DTO's non-nullable string properties
(Repository, Title, Body) are not initialized which can cause null-state drift;
update the GitHubIssuePayload class to initialize each property (Repository,
Title, Body) to an empty string (or a sensible default) in their declarations to
ensure they are never null at runtime and avoid nullability warnings.

---

Outside diff comments:
In `@SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs`:
- Around line 46-75: The code currently always sends bug reports via the Discord
webhook check and SubmitBugReportToDiscord; update the flow to prefer creating a
GitHub issue when the new GitHub submission path is available: add/check a
GitHub target environment/config (e.g., a GitHub repo or token env) and call a
new SubmitBugReportToGitHubIssue(...) method (or refactor existing submission
code) using the same metadata (authorName, areaName, areaTag, areaResref,
positionGroup, dateReported, playerId, message); if GitHub submission fails or
the GitHub config is absent, then fall back to the existing
SubmitBugReportToDiscord(...) path, preserving the error messages
(SendMessageToPC) and rate-limiting behavior (nextReportAllowed). Do the same
replacement for the secondary block referenced (lines ~84-121) so both code
paths use GitHub first and Discord as fallback, and ensure any
EnqueueDiscordWebhook(...) usages are only invoked when Discord is the chosen
fallback.

---

Nitpick comments:
In `@SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobProcessor.cs`:
- Around line 24-27: Add a Serilog error log right before returning when
BackgroundJob.TryCreate fails: log the entry.Id and the parsed error so
malformed payloads are traceable in production. Update the failure path in
BackgroundJobProcessor where BackgroundJob.TryCreate(entry, out var job, out var
error) returns false to call the logger (include entry.Id and error) immediately
before invoking _failureHandler.MoveToDeadLetterAsync(database, entry, error) or
at least before the return. Ensure the log uses the same logger instance used in
the class and formats the message with structured properties (entry.Id and
error).

In `@SWLOR.BackgroundServices/Infrastructure/IAppLogger.cs`:
- Around line 3-8: The custom IAppLogger interface (IAppLogger with Info,
Error(string), Error(string, Exception)) duplicates existing logging
abstractions; replace usages and registration of IAppLogger with the standard
Serilog.ILogger (or Microsoft.Extensions.Logging.ILogger<T>) by removing
IAppLogger, updating consumers to accept/consume Serilog.ILogger (or
ILogger<T>), and wiring the chosen logger in the host/worker registration so
dependency injection provides the real Serilog logger instance; ensure calls
currently targeting Info and Error map to the appropriate ILogger methods (e.g.,
Information, Error) and update any exception overload usage accordingly.

In `@SWLOR.Game.Server/Docker/docker-compose.yml`:
- Around line 38-44: The redis service needs a healthcheck and the
background-services should depend on redis being healthy; add a healthcheck
block to the redis service (use a command like "redis-cli PING" and set
interval, timeout and retries) and update background-services' depends_on to
wait for the redis service_healthy condition so the background worker
(background-services) only starts after Redis is ready to accept connections.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 70e15db8-7b33-4f2d-89f1-43da6d060f91

📥 Commits

Reviewing files that changed from the base of the PR and between e86f91e and 949b631.

📒 Files selected for processing (31)
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJob.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobFailureHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobProcessor.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobQueueNames.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobTypes.cs
  • SWLOR.BackgroundServices/BackgroundJobs/BackgroundJobWorker.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Handlers/DiscordWebhookJobHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Handlers/GitHubIssueJobHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/IBackgroundJobHandler.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Models/DiscordWebhookPayload.cs
  • SWLOR.BackgroundServices/BackgroundJobs/Models/GitHubIssuePayload.cs
  • SWLOR.BackgroundServices/Configuration/BackgroundServiceSettings.cs
  • SWLOR.BackgroundServices/Dockerfile
  • SWLOR.BackgroundServices/Infrastructure/ConsoleAppLogger.cs
  • SWLOR.BackgroundServices/Infrastructure/IAppLogger.cs
  • SWLOR.BackgroundServices/Infrastructure/JsonHttpContent.cs
  • SWLOR.BackgroundServices/Program.cs
  • SWLOR.BackgroundServices/SWLOR.BackgroundServices.csproj
  • SWLOR.Game.Server.sln
  • SWLOR.Game.Server/Docker/docker-compose.yml
  • SWLOR.Game.Server/Docker/swlor.env
  • SWLOR.Game.Server/Feature/ChatCommandDefinition/DMChatCommand.cs
  • SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/BugReportViewModel.cs
  • SWLOR.Game.Server/Feature/GuiDefinition/ViewModel/HoloNetViewModel.cs
  • SWLOR.Game.Server/Readmes/Deployment.md
  • SWLOR.Game.Server/SWLOR.Game.Server.csproj
  • SWLOR.Game.Server/Service/BackgroundJob.cs
  • SWLOR.Game.Server/Service/BackgroundJobService/BackgroundJobType.cs
  • SWLOR.Game.Server/Service/BackgroundJobService/DiscordWebhookPayload.cs
  • SWLOR.Game.Server/Service/BackgroundJobService/GitHubIssuePayload.cs
  • SWLOR.Game.Server/Service/DB.cs
💤 Files with no reviewable changes (1)
  • SWLOR.Game.Server/SWLOR.Game.Server.csproj

Comment on lines +40 to +44
private static int ParseAttempt(string? value)
{
return int.TryParse(value, out var attempt)
? attempt
: 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp parsed attempt to a non-negative value.

int.TryParse allows negative numbers, which can skew retry accounting.

🛠️ Proposed fix
 private static int ParseAttempt(string? value)
 {
-    return int.TryParse(value, out var attempt)
-        ? attempt
-        : 0;
+    return int.TryParse(value, out var attempt) && attempt >= 0
+        ? attempt
+        : 0;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static int ParseAttempt(string? value)
{
return int.TryParse(value, out var attempt)
? attempt
: 0;
private static int ParseAttempt(string? value)
{
return int.TryParse(value, out var attempt) && attempt >= 0
? attempt
: 0;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.BackgroundServices/BackgroundJobs/BackgroundJob.cs` around lines 40 -
44, The ParseAttempt method currently returns whatever int.TryParse yields,
which may be negative; update ParseAttempt to clamp any parsed value to a
non-negative number (zero or greater) before returning—e.g., after parsing in
ParseAttempt(string? value) ensure the returned attempt is Math.Max(0, attempt)
so negative inputs become 0 and retry accounting remains correct.

Comment on lines +3 to +18
public sealed class ConsoleAppLogger : IAppLogger
{
public void Info(string message)
{
Console.WriteLine($"{DateTime.UtcNow:O} [INF] {message}");
}

public void Error(string message)
{
Console.Error.WriteLine($"{DateTime.UtcNow:O} [ERR] {message}");
}

public void Error(string message, Exception exception)
{
Console.Error.WriteLine($"{DateTime.UtcNow:O} [ERR] {message}{Environment.NewLine}{exception}");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Serilog usage and package presence before refactor
fd -e csproj | xargs -I{} sh -c 'echo "--- {} ---"; rg -n "Serilog|PackageReference.*Serilog" "{}"'
rg -n --type cs "Log\.Information|Log\.Error|ILogger|Serilog"

Repository: zunath/SWLOR_NWN

Length of output: 810


🏁 Script executed:

# Check the actual content of the BackgroundServices .csproj file
cat SWLOR.BackgroundServices/SWLOR.BackgroundServices.csproj

Repository: zunath/SWLOR_NWN

Length of output: 469


🏁 Script executed:

# Examine the Log.cs file in SWLOR.Game.Server to understand the logging pattern
cat SWLOR.Game.Server/Service/Log.cs

Repository: zunath/SWLOR_NWN

Length of output: 3588


🏁 Script executed:

# Find where ConsoleAppLogger is being used
rg -n "ConsoleAppLogger" --type cs

Repository: zunath/SWLOR_NWN

Length of output: 251


🏁 Script executed:

# Check the IAppLogger interface to understand its contract
fd -name "IAppLogger*" --type f

Repository: zunath/SWLOR_NWN

Length of output: 291


🏁 Script executed:

# Find IAppLogger interface
rg -n "interface IAppLogger" --type cs

Repository: zunath/SWLOR_NWN

Length of output: 148


🏁 Script executed:

# Find all usages of IAppLogger
rg -n "IAppLogger" --type cs

Repository: zunath/SWLOR_NWN

Length of output: 977


🏁 Script executed:

# Check what Program.cs does with the logger
cat -n SWLOR.BackgroundServices/Program.cs

Repository: zunath/SWLOR_NWN

Length of output: 1367


🏁 Script executed:

# View the IAppLogger interface definition
cat SWLOR.BackgroundServices/Infrastructure/IAppLogger.cs

Repository: zunath/SWLOR_NWN

Length of output: 286


Replace direct console writes with Serilog-backed logging.

This implementation bypasses the project logging standard and loses structured log metadata consistency across services. The SWLOR.BackgroundServices project should add the Serilog NuGet package and update ConsoleAppLogger to use Serilog's comprehensive logging capabilities, consistent with how SWLOR.Game.Server implements logging via the Log service.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.BackgroundServices/Infrastructure/ConsoleAppLogger.cs` around lines 3 -
18, ConsoleAppLogger currently writes directly to the console in Info and Error
methods; replace those calls with Serilog so logs are consistent and structured:
add the Serilog NuGet and update the ConsoleAppLogger class to call
Serilog.Log.Information for Info(string message), Serilog.Log.Error for
Error(string message) and Serilog.Log.Error(exception, message) for the
Error(string message, Exception exception) overload, removing direct
Console/Console.Error usage and relying on Serilog configuration (timestamp,
sinks, enrichment) like other services that use the Log service.

Comment on lines +64 to +67
_ = Task.Run(() => EnqueueAsync(type, entries, context));

return true;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Enqueue* reports success before the job is actually enqueued.

Line 64 offloads Redis write to fire-and-forget, and Line 66 returns true immediately. If Redis write fails later (Line 85+), callers still think the job was accepted. That creates false-positive UX and silent loss for bug reports.

Also applies to: 75-88

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.Game.Server/Service/BackgroundJob.cs` around lines 64 - 67, The method
currently fire-and-forgets the Redis write by calling Task.Run(() =>
EnqueueAsync(type, entries, context)) and returns true immediately, causing
callers to get false success; change the public enqueue methods to await the
background enqueue operation and propagate its success/failure: call and await
EnqueueAsync(type, entries, context) (or change the method signature to return
Task<bool> if not already) and return the actual boolean result from
EnqueueAsync (or catch exceptions and return false after logging), ensuring the
caller only receives true when the Redis enqueue succeeded; locate usages of
Enqueue* and EnqueueAsync in BackgroundJob.cs to update their signatures and
callers accordingly.

Comment on lines +91 to +99
private static string BuildLogContext<TPayload>(TPayload payload)
{
switch (payload)
{
case DiscordWebhookPayload discord:
return $"authorName='{Truncate(discord.AuthorName)}', title='{Truncate(discord.Title)}', description='{Truncate(discord.Description)}', threadId='{Truncate(discord.ThreadId)}', createThread='{discord.CreateThread}'";
case GitHubIssuePayload gitHub:
return $"repository='{Truncate(gitHub.Repository)}', title='{Truncate(gitHub.Title)}'";
default:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Failure logs are including user-supplied message content.

BuildLogContext logs truncated description/title. Those fields can contain sensitive data from bug reports and end up in error logs. Remove free-form content from log context and keep only non-sensitive identifiers.

Proposed change
 case DiscordWebhookPayload discord:
-    return $"authorName='{Truncate(discord.AuthorName)}', title='{Truncate(discord.Title)}', description='{Truncate(discord.Description)}', threadId='{Truncate(discord.ThreadId)}', createThread='{discord.CreateThread}'";
+    return $"authorName='{Truncate(discord.AuthorName)}', threadId='{Truncate(discord.ThreadId)}', createThread='{discord.CreateThread}'";
 case GitHubIssuePayload gitHub:
-    return $"repository='{Truncate(gitHub.Repository)}', title='{Truncate(gitHub.Title)}'";
+    return $"repository='{Truncate(gitHub.Repository)}'";

Also applies to: 70-70, 87-87

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.Game.Server/Service/BackgroundJob.cs` around lines 91 - 99,
BuildLogContext currently includes free-form fields (Title, Description,
AuthorName) that may contain sensitive user-supplied text; update the switch in
BuildLogContext to stop logging truncated message content and only emit
non-sensitive identifiers. For DiscordWebhookPayload return a context string
containing only threadId and createThread (e.g., threadId and CreateThread flag)
and omit AuthorName/Title/Description, and for GitHubIssuePayload return only
repository (omit issue Title); adjust the returned strings in the
BuildLogContext method to remove Truncate(...) usage for free-form fields and
keep only safe identifiers.

Comment on lines +8 to +14
public string Repository { get; set; }

[JsonProperty("title")]
public string Title { get; set; }

[JsonProperty("body")]
public string Body { get; set; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Initialize non-nullable DTO strings to prevent null-state drift.

Repository, Title, and Body are non-nullable but currently uninitialized. This can surface nullability warnings and null values at runtime on partial/malformed payloads.

Proposed fix
 public class GitHubIssuePayload
 {
     [JsonProperty("repository")]
-    public string Repository { get; set; }
+    public string Repository { get; set; } = string.Empty;

     [JsonProperty("title")]
-    public string Title { get; set; }
+    public string Title { get; set; } = string.Empty;

     [JsonProperty("body")]
-    public string Body { get; set; }
+    public string Body { get; set; } = string.Empty;
 }

Based on learnings: "In C# files within the SWLOR.Game.Server and SWLOR.NWN.API projects, avoid using acronyms in variable names; prefer descriptive names..."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public string Repository { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("body")]
public string Body { get; set; }
public string Repository { get; set; } = string.Empty;
[JsonProperty("title")]
public string Title { get; set; } = string.Empty;
[JsonProperty("body")]
public string Body { get; set; } = string.Empty;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SWLOR.Game.Server/Service/BackgroundJobService/GitHubIssuePayload.cs` around
lines 8 - 14, The GitHubIssuePayload DTO's non-nullable string properties
(Repository, Title, Body) are not initialized which can cause null-state drift;
update the GitHubIssuePayload class to initialize each property (Repository,
Title, Body) to an empty string (or a sensible default) in their declarations to
ensure they are never null at runtime and avoid nullability warnings.

@zunath zunath changed the title Send bug reports to GitHub Issues instead of Discord webhook Offload discord postings to background worker May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant