Issue
Summary
When aider is driven headlessly with --message, any input that starts with / is processed as an in-chat command — and several of those commands (/model, /editor-model, /weak-model, /chat-mode, /ask, /code, /architect, ...) work by raising SwitchCoder. In main() that exception is swallowed with a bare pass, so aider prints its banner and exits 0 having done nothing:
aider/main.py:
if args.message:
io.add_to_input_history(args.message)
io.tool_output()
try:
coder.run(with_message=args.message)
except SwitchCoder:
pass
analytics.event("exit", reason="Completed --message")
return
The command never runs, the message is never sent, and nothing tells the user or the calling script what happened. Compare with the interactive loop, which properly handles SwitchCoder by rebuilding the coder (main.py while True: / except SwitchCoder as switch:).
Reproduction (verified on current main)
$ aider --message "/model gpt-4o" --no-git --yes-always
Aider v0.86.3.dev53+g5dc9490bb
Main model: gpt-4o-mini ... <- original model, unchanged
...
EXITCODE=0
No error, no warning, exit code 0. The requested model switch silently did not happen.
The same happens for every SwitchCoder-raising command passed via --message, including the very common scripting pattern:
aider --message "/ask explain this module" → silent no-op, exit 0
aider --message "/architect refactor X" → silent no-op, exit 0
(commands.py raises SwitchCoder from cmd_model, cmd_editor_model, cmd_weak_model, cmd_chat_mode, cmd_ask, cmd_code, cmd_architect.)
Impact
Headless/CI users can't distinguish "command executed" from "input ignored" — both exit 0. This is the same class of headless trap as #5552 (exit 0 after fatal API errors) but a different mechanism and location: here the swallow is explicit at main.py's --message branch.
Note the adjacent --message-file path has open PRs handling SwitchCoder; the plain --message branch still discards it.
Expected behavior
Either:
- Apply the switch like the interactive loop does (rebuild the coder and continue with the remaining message), if anything follows the command; or at minimum
- Print a clear error ("
/model is only supported in interactive mode") and exit non-zero, mirroring what /load does for nested switches (commands.py: "Command '...' is only supported in interactive mode, skipping.").
Happy to send a PR implementing option 2 (small, safe) or option 1.
Version and model info
- Aider v0.86.3.dev53+g5dc9490bb (main @
5dc9490bb)
- Python 3.12.10, Windows 11; behavior is platform-independent
- Any model; triggered purely by CLI argument + in-chat command combination
Issue
Summary
When aider is driven headlessly with
--message, any input that starts with/is processed as an in-chat command — and several of those commands (/model,/editor-model,/weak-model,/chat-mode,/ask,/code,/architect, ...) work by raisingSwitchCoder. Inmain()that exception is swallowed with a barepass, so aider prints its banner and exits 0 having done nothing:aider/main.py:The command never runs, the message is never sent, and nothing tells the user or the calling script what happened. Compare with the interactive loop, which properly handles
SwitchCoderby rebuilding the coder (main.pywhile True:/except SwitchCoder as switch:).Reproduction (verified on current main)
No error, no warning, exit code 0. The requested model switch silently did not happen.
The same happens for every
SwitchCoder-raising command passed via--message, including the very common scripting pattern:aider --message "/ask explain this module"→ silent no-op, exit 0aider --message "/architect refactor X"→ silent no-op, exit 0(
commands.pyraisesSwitchCoderfromcmd_model,cmd_editor_model,cmd_weak_model,cmd_chat_mode,cmd_ask,cmd_code,cmd_architect.)Impact
Headless/CI users can't distinguish "command executed" from "input ignored" — both exit 0. This is the same class of headless trap as #5552 (exit 0 after fatal API errors) but a different mechanism and location: here the swallow is explicit at
main.py's--messagebranch.Note the adjacent
--message-filepath has open PRs handlingSwitchCoder; the plain--messagebranch still discards it.Expected behavior
Either:
/modelis only supported in interactive mode") and exit non-zero, mirroring what/loaddoes for nested switches (commands.py: "Command '...' is only supported in interactive mode, skipping.").Happy to send a PR implementing option 2 (small, safe) or option 1.
Version and model info
5dc9490bb)