Skip to content

fix: Refused a prefix command typed without its subcommand instead of failing - #219

Merged
Kkkermit merged 1 commit into
developfrom
testify/bugfix-12
Nov 13, 2026
Merged

Kkkermit merged 1 commit into
developfrom
testify/bugfix-12

Conversation

@Kkkermit

Copy link
Copy Markdown
Owner

What this changes

  • runChecks reads the subcommand once, and refuses a prefix run that names none the command has. t?role @someone @Role now gets a short reply that deletes itself: "That command needs one of these after its name: add or remove." The refusal comes after the owner-only, pause, blacklist and switch gates and before the cooldown, so a typo no longer starts one.
  • The music and casino gates take the subcommand they're given rather than reading it again, so t?music nonsense and t?casino nonsense are refused the same way.
  • pickSubcommand is a new shared reply in all eight languages. It lists the subcommands with eitherOf, so each language joins them its own way.
  • AGENTS.md §5 notes the refusal, and the comment on the prefix reader's lazy parse now says what answers it.

Why

This was logged in production:

[MESSAGE_HANDLER_ERROR] A message handler failed. err=Pick one of: `add`, `remove`. handler=prefixCommands
UserFacingError: Pick one of: `add`, `remove`.
    at get state (/app/dist/core/prefix.js:75:15)
    at PrefixOptions.getSubcommand (/app/dist/core/prefix.js:115:33)
    at chosenSubcommand (/app/dist/core/checks.js:179:38)
    at runChecks (/app/dist/core/checks.js:79:52)

The prefix reader throws a UserFacingError the first time it is read when the words typed name no subcommand. That was meant to happen inside runCommand, where it is answered to the person. But runChecks reads the subcommand earlier, for the subcommand's own permissions, and for the music and casino gates before that. Its private chosenSubcommand didn't catch the error, so it escaped the handler. The person got no reply, and the operator got an error log for a typo.

Slash commands were never affected: Discord makes the reader pick a subcommand, and discord.js's getSubcommand(false) doesn't throw.

How to verify

  • New tests in tests/core/checks.test.ts run runChecks on a real PrefixInteraction:
    • t?role @someone and a bare t?role are refused with the subcommands to pick from, and a corrected t?role add @someone straight after is let through, so the typo cost no cooldown.
    • The music and casino commands with an unknown subcommand are each refused, naming theirs.
    • With the old checks.ts both tests fail with UserFacingError: Pick one of: …, the error from the log.
  • npm run check passes locally with 4,046 tests; the 30 database suites are skipped there. CI passed on this branch, with the database suites included.
  • In Discord: t?role with no subcommand answers with the refusal, which deletes itself, and nothing is logged at error. Not tried against live Discord.

Checklist

  • The branch is cut from develop, and this pull request targets develop
  • npm run check passes
  • Tests cover the new logic, and were shown to fail without the fix
  • No read-modify-save() on numeric fields
  • Embeds come from the embed() factory
  • No raw console.*
  • Every promise is awaited
  • No new timers
  • No new custom IDs
  • No new environment variables
  • No command changed, so docs/commands.md is unchanged
  • Documentation updated: AGENTS.md §5

Generated by Claude Code

@Kkkermit
Kkkermit merged commit 4d9682e into develop Nov 13, 2026
8 checks passed
@Kkkermit
Kkkermit deleted the testify/bugfix-12 branch November 13, 2026 15:06
@Kkkermit Kkkermit mentioned this pull request Nov 13, 2026
13 of 15 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant