Skip to content

fix(gateway): reject multi-line /model with clear error - #23300

Open
dockerpaula wants to merge 2 commits into
NousResearch:mainfrom
dockerpaula:devin/1778349866-model-multiline-error
Open

fix(gateway): reject multi-line /model with clear error#23300
dockerpaula wants to merge 2 commits into
NousResearch:mainfrom
dockerpaula:devin/1778349866-model-multiline-error

Conversation

@dockerpaula

Copy link
Copy Markdown

What does this PR do?

Fixes #22716 — a multi-line /model command (e.g. Shift+Enter in Element/Matrix produces /model openai/gpt-5.5\nFollow-up question in a single send) is currently rejected with the misleading error Model names cannot contain spaces, and the user's follow-up text is silently swallowed. The error message is wrong in two ways: there are no spaces in the model name, and the real problem is that \n was forwarded into the model-name parser at all.

This PR detects the multi-line form early in _handle_model_command and returns a clear, actionable message that quotes the intended single-line command back to the user, e.g.:

`/model` is a single-line command. Send `/model openai/gpt-5.5` first, then send your follow-up message separately.

The fix is intentionally surgical — no behavior change for legitimate single-line /model invocations, and no change to other commands that do accept multi-line text (/queue, /steer, /agent, etc.) because the new helper only short-circuits when called from the /model handler.

Related Issue

Fixes #22716

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • gateway/platforms/base.py — added MessageEvent.get_command_remainder() helper. Returns the text that follows the first newline of a command message (e.g. /model X\nFoo"Foo"), with strip semantics; returns "" for non-commands, single-line commands, command-only messages, or messages whose remainder is whitespace-only. Sits next to the existing get_command_args()/is_command() helpers.
  • gateway/run.py — added an early multi-line detection block in _handle_model_command() that uses get_command_remainder() to short-circuit before reaching the model-name parser. Handles two distinct multi-line shapes:
    • /model X\nFooget_command_args() returns "X\nFoo", so the suggestion is `/model X`.
    • /model\nFooget_command_args() consumes the \n as the arg separator and returns "Foo", so the suggestion falls back to `/model <name>`.
  • tests/gateway/test_platform_base.py — added TestMessageEventGetCommandRemainder (8 tests) covering: single-line no-remainder, command-only no-remainder, multi-line returns remainder, remainder is stripped, inner newlines in remainder preserved, blank-only remainder returns "", non-command returns "", and a non-regression check that get_command_args() still returns the full multi-line tail for commands like /queue line1\nline2\nline3 that legitimately accept multi-line text.
  • tests/gateway/test_model_command_multiline.py (new file, 4 tests) — async regression tests for _handle_model_command() covering: the canonical /model X\nFoo case, multi-line with flags (/model X --global\nFoo), benign trailing-newline (/model X\n with no follow-up — should NOT trigger the multi-line error), and the empty-first-line fallback (/model\nFoo — should suggest the generic /model <name> form).
  • scripts/release.py — adds dockerpaula@gmail.com → dockerpaula to AUTHOR_MAP so the contributor-attribution check resolves the email when generating release notes.

How to Test

Reproduction (before the fix):

# In any platform that delivers a single message containing \n (Element/Matrix
# Shift+Enter, Slack composer multi-line, etc.):
/model openai/gpt-5.5
What is 2+2?

Before — the gateway replies: Model names cannot contain spaces (and the question is dropped).
After — the gateway replies: `/model` is a single-line command. Send `/model openai/gpt-5.5` first, then send your follow-up message separately.

Automated:

# 1. New regression tests for both the helper and the handler — 102 passed, 2 skipped
scripts/run_tests.sh tests/gateway/test_platform_base.py tests/gateway/test_model_command_multiline.py -q

# 2. All existing /model command tests — 113 passed, 2 skipped — confirms no regression
scripts/run_tests.sh tests/gateway/test_model_command_*.py tests/gateway/test_model_switch_*.py tests/gateway/test_discord_model_picker.py -q

# 3. Lint + Windows-footguns
ruff check gateway/platforms/base.py gateway/run.py tests/gateway/test_platform_base.py tests/gateway/test_model_command_multiline.py scripts/release.py
python scripts/check-windows-footguns.py --all

All green locally.

Checklist

Code

Documentation & Housekeeping

  • N/A — user-visible behavior change is the error message itself; no docs page documented the previous wrong message
  • N/A — no config keys added/changed
  • N/A — no architecture or workflow change
  • Cross-platform: pure-Python string handling (str.partition, str.split); no platform-specific calls
  • N/A — no tool descriptions/schemas changed

Screenshots / Logs

$ pytest tests/gateway/test_platform_base.py tests/gateway/test_model_command_multiline.py -q
........................................................................ [ 69%]
.........................s.s....                                         [100%]
102 passed, 2 skipped in 3.09s

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery labels May 10, 2026
When a user sent a single message that began with `/model X` followed by
a newline and additional text — e.g. via Element/Matrix's Shift+Enter —
the gateway forwarded the entire remainder into the model name parser.
The parser then replied with the misleading error
"Model names cannot contain spaces", even though the input contained no
spaces (just a newline). The follow-up question was silently dropped and
no model switch happened.

Refuse the multi-line form early in `_handle_model_command` with an
accurate, copy-paste-ready hint:

    /model is a single-line command. Send `/model openai/gpt-5.5` first,
    then send your follow-up message separately.

Detection uses a new `MessageEvent.get_command_remainder()` helper that
returns the text after the first newline of a command message. Other
single-line commands (`/topic`, `/style`, `/voice`, …) can adopt the
same idiom in follow-up patches; `get_command_args()` is intentionally
unchanged so multi-line-friendly commands like `/queue`, `/steer` and
`/agent` keep working as before.

Both forms are covered:

* `/model X\nFoo`   — newline survives `split(maxsplit=1)` inside args
* `/model\nFoo`     — newline is consumed as the arg separator

Refs NousResearch#22716.
Adds an AUTHOR_MAP entry so the contributor-attribution check in
scripts/release.py can resolve dockerpaula@gmail.com to the GitHub
login dockerpaula when generating release notes.

Refs the multi-line /model fix in this PR (issue NousResearch#22716).
@dockerpaula
dockerpaula force-pushed the devin/1778349866-model-multiline-error branch from 9c60b56 to 16f820d Compare May 10, 2026 16:48

@teknium1 teknium1 left a comment

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.

Thanks for the focused regression coverage. The underlying bug still exists on current main: gateway/platforms/base.py:1804 retains the multi-line argument tail, and the current handler at gateway/slash_commands.py:1420 passes it into flag parsing and model validation.

Problems

  • tests/gateway/test_model_command_multiline.py:90 only proves that a trailing newline does not return the new guidance text. It also passes for an unrelated model-switch error, so it does not establish the documented “switch must proceed” behavior.

Suggested changes

  • During salvage, place the early guard in the current handler at gateway/slash_commands.py:1420; 619bd782 moved _handle_model_command out of gateway/run.py.
  • Make the trailing-newline test control or mock the normal switch pipeline and assert that it receives the first-line model name.

This is an automated hermes-sweeper review.

# assert that the response is NOT the new multi-line guidance error.
result = await _make_runner()._handle_model_command(event)

assert result is None or "single-line command" not in result.lower()

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.

This assertion accepts any non-guidance failure, including a validation or provider error, so it does not prove the stated contract that a trailing newline reaches the normal successful switch path. Please control the switch dependency and assert the first-line model argument is used.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Matrix gateway: slash command + free-text question in a single multi-line message is rejected, blocking inline orchestration patterns

3 participants