`ssh <host>` is a login shell, but `ssh <host> <command>` is not?

OpenSSH (most likely what you're running) decides whether or not to create a login shell, and it only does so if you are not running a specific command. From man ssh:

 If command is specified, it is executed on the remote host instead of a
 login shell.

So it's an implementation choice for the ssh server whether it wants to create a login shell or not, and if you give a command to run, it does not.

While ssh does perform a login, if you're having it execute a command and exit, it's really much more similar to creating a shell just to run that command than it is to getting a login environment. It seems, given that, that the people writing OpenSSH decided to treat it like that sort of task.

They create a non-interactive, non-login shell to execute the command, because that's the spirit of running a command in another context/shell. Normally, though, non-interactive shells would not automatically source ~/.bashrc which is clearly happening here. bash is actually trying to help us out here. From the docs

Invoked by remote shell daemon

Bash attempts to determine when it is being run with its standard input connected to a network connection, as when executed by the remote shell daemon, usually rshd, or the secure shell daemon sshd. If Bash determines it is being run in this fashion, it reads and executes commands from ~/.bashrc, if that file exists and is readable. It will not do this if invoked as sh. The --norc option may be used to inhibit this behavior, and the --rcfile option may be used to force another file to be read, but neither rshd nor sshd generally invoke the shell with those options or allow them to be specified.


The why of this behavior lies at a lower level than shells: ssh host (the "login shell" case) uses a pseudoterminal on the remote host, to communicate between the sshd server process and the shell; ssh host command uses pipes between sshd and command, instead. Pseudoterminals are necessary to make interactive use of a command interpreter, such as a shell, or the "read-eval-print" mode of a scripting language; they implement a bunch of human-friendly features like being able to backspace over typos. But they have more overhead and (depending on configuration) do not allow arbitrary data to pass through unmodified, so SSH avoids using them when interaction is not going to be happening.

Sometimes SSH's command/no command heuristic gets this wrong; it can be overridden with the -t and -T switches. For instance, to log into a remote machine and immediately reattach a suspended screen session, you need to do ssh -t host screen -R; ssh host screen -R will cause screen to complain about not being connected to a terminal. I can't think of a situation when you would actually want to use -T, but it's there if you ever do find one.