Send permission bit fields as decimal, not binary - #134
Open
mikield wants to merge 1 commit into
Open
Conversation
Bitwise::getBitSet() returns decbin(), and three payloads were sending its result to Discord: a command's default_member_permissions, and the allow and deny of a channel permission overwrite. Discord reads all three as decimal. Asking for ADMINISTRATOR, which is 1 << 3, therefore sent "1000". Discord read that as one thousand, which is ADMINISTRATOR together with MANAGE_GUILD, ADD_REACTIONS, VIEW_AUDIT_LOG, PRIORITY_SPEAKER and STREAM. Every command registered with default permissions has been granting five permissions nobody asked for, and channel overwrites have been allowing and denying the wrong things. The three now send the decimal value, and the matching getters read it back the same way rather than through fromBitSet. getBitSet itself is untouched: a binary representation is a reasonable thing to expose, and it has its own test. It just is not what goes on the wire. The reason this survived is that the test asserted the payload equalled getBitSet(), so it described the behaviour rather than the requirement and passed either way. Both tests now assert the literal value Discord expects. While there, EditPermissionsBuilderTest built its Bitwise with new Bitwise(1 << 1, 1 << 2, 1 << 3). The constructor takes a single int, so the second and third arguments were dropped and the test only ever exercised one flag; it now uses Bitwise::from.
mikield
added a commit
to Tempcord/framework
that referenced
this pull request
Aug 22, 2026
…sions Two fixes that come from the same place. Registration used to POST each command individually. That costs a request per command, and Discord's create endpoint only ever adds: a command deleted from the code stayed registered forever and users kept seeing it. Registration now groups commands into the sets Discord replaces atomically — the global set, and one set per guild — and sends each as a single PUT. Anything absent from the set is removed, which is what makes a deleted command actually disappear. #[Command(permissions: [])] was collected all the way into CommandDefinition and then never read, so setting it did nothing. It is now typed as Fenrir's Permission enum rather than a list of strings, and reaches the payload. Two things are worth explaining rather than leaving to be discovered: The permission bit field is written onto the payload directly instead of going through CommandBuilder::setDefaultMemberPermissions, which sends the binary representation of the bit field where Discord expects the decimal one. Asking for ADMINISTRATOR that way grants five permissions nobody asked for. Fixed upstream in dc-Ragnarok/Fenrir#134; the direct write is correct either way, so it does not need reverting when that lands. Fenrir has no bulk overwrite method yet and keeps its HTTP client private on both Discord and Rest, so CommandRegistrar builds one for itself from the token already in TempcordConfig. Registration runs once, before the gateway opens, so the two clients never compete for a rate limit bucket. The method is added in dc-Ragnarok/Fenrir#133 and this can move over to it once released.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Bitwise::getBitSet()returnsdecbin(), and three payloads send its result straight to Discord:CommandBuilder::setDefaultMemberPermissions()→default_member_permissionsEditPermissionsBuilder::setAllow()→allowEditPermissionsBuilder::setDeny()→denyDiscord reads all three as decimal. So asking for
ADMINISTRATOR, which is1 << 3, sends"1000"— and Discord reads that as one thousand:Decimal 1000 is
ADMINISTRATOR | MANAGE_GUILD | ADD_REACTIONS | VIEW_AUDIT_LOG | PRIORITY_SPEAKER | STREAM.Every command registered with default permissions has been granting five permissions nobody asked for, and channel permission overwrites have been allowing and denying the wrong things.
The fix
All three send the decimal value, and the matching getters read it back the same way rather than through
fromBitSet.getBitSet()itself is untouched — a binary representation is a reasonable thing to expose and it has its own test. It just isn't what goes on the wire.Why it survived
The test asserted the payload equalled
getBitSet():That describes the behaviour rather than the requirement, so it passed either way. Both tests now assert the literal value Discord expects.
While there:
EditPermissionsBuilderTestbuilt its Bitwise withnew Bitwise(1 << 1, 1 << 2, 1 << 3). The constructor takes a single int, so the second and third arguments were silently dropped and the test only ever exercised one flag. It now usesBitwise::from.Notes
This is split out of #133 deliberately — it's a small correctness fix against
masterthat stands on its own and is worth releasing without waiting on that larger branch. #133 contains this same commit; whichever merges first, the other rebases cleanly.composer test(526),composer csandutil/verify-namespacing.shall pass.