-
Notifications
You must be signed in to change notification settings - Fork 6
Fix promotion blockers, identify audit log executors, track unban/timeout #255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There’s a race condition in attributing bans, because the Audit Log Entry object isn’t created immediately as the Ban event fires. We’ll need to wait for a few seconds to make sure that the Audit Log Entry isn’t still pending before we post. We could perhaps change this to post immediately and edit the message once attribution details come back.
Extends the mod action logging system to capture timeout and unban events. Timeout logs include human-readable duration, and only manual timeout removals (before natural expiry) are logged. Refactors handlers to use Effect-TS with thin async wrappers that execute Effects. Extracts shared audit log fetching logic into a reusable helper. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Updates public-facing promotion blockers (title + invite link) and expands moderation reporting to capture more actions (unban/timeout) with better attribution via audit logs.
Changes:
- Update app page title metadata for production branding.
- Update the “Add to server” invite link and supporting UI entry point.
- Refactor moderation/audit-log reporting to use Effect-based handlers and add logging for unbans and timeouts.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| app/root.tsx | Updates the app’s <title> metadata for branding/promotion readiness. |
| app/components/DiscordLayout.tsx | Updates the Discord OAuth invite URL used by the dashboard UI. |
| app/commands/report/modActionLogger.ts | Refactors mod-action event handling, adds audit log retries, and introduces unban/timeout tracking. |
| app/commands/report/modActionLog.ts | Extends the mod-action report union and formats new action types in user-thread logs. |
| app/commands/report/automodLog.ts | Exports logAutomod for reuse by the updated logger. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const entry = findEntry(auditLogs.entries); | ||
| if (entry?.executor) { | ||
| yield* logEffect("debug", "ModActionLogger", `record found`, { | ||
| attempt: attempt + 1, | ||
| }); | ||
| return entry; | ||
| } |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetchAuditLogEntry only returns a found audit-log record when entry.executor is truthy. Audit log entries can exist without an executor (or the executor can be unavailable), which means this will incorrectly treat real bans/kicks/unbans as “not found” (e.g., kicks become left) and will also drop reason data. Return when an entry is found (and let downstream code handle executor === null) instead of gating on executor.
| // Determine if this is a timeout applied or removed | ||
| const isTimeoutApplied = newTimeout !== null && newTimeout > Date.now(); | ||
| const isTimeoutRemoved = | ||
| oldTimeout !== null && oldTimeout > Date.now() && newTimeout === null; | ||
|
|
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isTimeoutApplied is true for any member update while a timeout is active (newTimeout > Date.now()), even when oldTimeout === newTimeout. Since GuildMemberUpdate fires for many unrelated changes, this will spam duplicate “timeout” logs. Detect an actual timeout change (e.g., compare oldTimeout vs newTimeout, or compute hadTimeout/hasTimeout and only log when transitioning).
| // Determine if this is a timeout applied or removed | |
| const isTimeoutApplied = newTimeout !== null && newTimeout > Date.now(); | |
| const isTimeoutRemoved = | |
| oldTimeout !== null && oldTimeout > Date.now() && newTimeout === null; | |
| // Determine if this is a timeout applied, updated, or removed | |
| const hadTimeout = | |
| oldTimeout !== null && oldTimeout > Date.now(); | |
| const hasTimeout = | |
| newTimeout !== null && newTimeout > Date.now(); | |
| // Treat as "applied" if a timeout has become active or its expiry changed | |
| const isTimeoutApplied = | |
| hasTimeout && (!hadTimeout || oldTimeout !== newTimeout); | |
| // Treat as "removed" if a previously active timeout is no longer active | |
| const isTimeoutRemoved = hadTimeout && !hasTimeout; |
|
|
||
| const reasonText = reason ? ` ${reason}` : " for no reason"; | ||
| const durationText = | ||
| actionType === "timeout" ? ` for ${report.duration}` : ""; |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
durationText reads report.duration based on actionType === "timeout", but actionType was destructured from report, so TypeScript typically won’t narrow report here (and duration doesn’t exist on the other union variants). This is likely a type error under strict. Narrow on report.actionType (or use an if (report.actionType === "timeout") block) before accessing report.duration.
| actionType === "timeout" ? ` for ${report.duration}` : ""; | |
| report.actionType === "timeout" ? ` for ${report.duration}` : ""; |
| { | ||
| userId, | ||
| guildId: guild.id, | ||
| ruleId: autoModerationRule?.name, |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the structured log context for the timeout-skip branch, the key is ruleId but the value being logged is autoModerationRule?.name. This makes the log payload misleading; use ruleName for the name, or log the actual rule ID if you want ruleId.
| ruleId: autoModerationRule?.name, | |
| ruleName: autoModerationRule?.name, |
Preview environment removedThe preview for this PR has been cleaned up. |
Partially addresses #252