From b38616e0519412940bd61ea8a0258d4cc7810846 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Mon, 17 Aug 2026 20:52:09 +0700 Subject: [PATCH] fix(auto-delete): guard nickname reset on role hierarchy + surface LLM parse errors - resetOffensiveNickname: skip when target role sits above bot (member.manageable) instead of hammering a doomed setNickname PATCH that Discord rejects with 50013 'Missing Permissions'. Log the Discord error code on failure for clear diagnosis. - llmCaller: include contentPreview (first 200 chars) in the parse- failure warning so non-JSON LLM responses are debuggable. --- .../ai-moderation/autoDeleteManager.ts | 22 +++++++++++++++++++ .../src/modules/ai-moderation/llmCaller.ts | 1 + 2 files changed, 23 insertions(+) diff --git a/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts b/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts index a803cb0..6418ec0 100644 --- a/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts +++ b/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts @@ -74,6 +74,23 @@ export async function resetOffensiveNickname( return false; } const member = await guild.members.fetch(userId); + // Discord rejects setNickname with "Missing Permissions" (code 50013) + // whenever the target's top role sits above the bot in the role + // hierarchy — even when the bot has MANAGE_NICKNAMES. Guard on + // `manageable` (hierarchy-aware) so we skip with a clear reason + // instead of hammering a doomed PATCH on every message. + if (!member.manageable) { + logger.debug( + { + messageId, + guildId, + userId, + reason: "target role above bot in hierarchy (Discord 50013)", + }, + "Nick reset skipped: member not manageable by bot", + ); + return false; + } // setNickname(null) = remove nickname → Discord shows global username await member.setNickname(null, "[auto] nickname melanggar aturan server"); recentNicknameResets.set(cooldownKey, Date.now()); @@ -83,12 +100,17 @@ export async function resetOffensiveNickname( ); return true; } catch (error) { + const errCode = + error instanceof Error && "code" in error + ? (error as { code?: number | string }).code + : undefined; logger.warn( { messageId, guildId, userId, error: error instanceof Error ? error.message : String(error), + code: errCode, }, "Nick reset failed", ); diff --git a/services/discord-gateway/src/modules/ai-moderation/llmCaller.ts b/services/discord-gateway/src/modules/ai-moderation/llmCaller.ts index f2f471a..caed53f 100644 --- a/services/discord-gateway/src/modules/ai-moderation/llmCaller.ts +++ b/services/discord-gateway/src/modules/ai-moderation/llmCaller.ts @@ -119,6 +119,7 @@ export async function callModerationLLM( { error: state.lastParseError, contentLength: rawContent.length, + contentPreview: rawContent.slice(0, 200), targetIds, model: config.AI_LLM_MODEL, },