multiSSH3 6.39__tar.gz → 6.40__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {multissh3-6.39/multiSSH3.egg-info → multissh3-6.40}/PKG-INFO +43 -6
  2. {multissh3-6.39 → multissh3-6.40}/README.md +42 -5
  3. {multissh3-6.39 → multissh3-6.40}/mssh_complete.py +168 -60
  4. {multissh3-6.39 → multissh3-6.40/multiSSH3.egg-info}/PKG-INFO +43 -6
  5. {multissh3-6.39 → multissh3-6.40}/multiSSH3.egg-info/SOURCES.txt +1 -0
  6. {multissh3-6.39 → multissh3-6.40}/multiSSH3.py +135 -42
  7. {multissh3-6.39 → multissh3-6.40}/tests/conftest.py +4 -0
  8. multissh3-6.40/tests/shells/Dockerfile.el9 +19 -0
  9. {multissh3-6.39 → multissh3-6.40}/tests/shells/run_matrix.sh +10 -7
  10. multissh3-6.40/tests/test_complete_blesh.py +205 -0
  11. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_engine.py +28 -0
  12. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_keys.py +7 -1
  13. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_shells.py +338 -38
  14. {multissh3-6.39 → multissh3-6.40}/tests/test_config_cli.py +237 -5
  15. {multissh3-6.39 → multissh3-6.40}/tests/test_control_master_config.py +93 -0
  16. multissh3-6.39/tests/test_complete_blesh.py +0 -121
  17. {multissh3-6.39 → multissh3-6.40}/LICENSE +0 -0
  18. {multissh3-6.39 → multissh3-6.40}/MANIFEST.in +0 -0
  19. {multissh3-6.39 → multissh3-6.40}/multiSSH3.egg-info/dependency_links.txt +0 -0
  20. {multissh3-6.39 → multissh3-6.40}/multiSSH3.egg-info/entry_points.txt +0 -0
  21. {multissh3-6.39 → multissh3-6.40}/multiSSH3.egg-info/top_level.txt +0 -0
  22. {multissh3-6.39 → multissh3-6.40}/pytest.ini +0 -0
  23. {multissh3-6.39 → multissh3-6.40}/setup.cfg +0 -0
  24. {multissh3-6.39 → multissh3-6.40}/setup.py +0 -0
  25. {multissh3-6.39 → multissh3-6.40}/tests/bench_complete.py +0 -0
  26. {multissh3-6.39 → multissh3-6.40}/tests/complete_shell_harness.py +0 -0
  27. {multissh3-6.39 → multissh3-6.40}/tests/data/run_command_golden.json +0 -0
  28. {multissh3-6.39 → multissh3-6.40}/tests/replay.py +0 -0
  29. {multissh3-6.39 → multissh3-6.40}/tests/shells/Dockerfile.bash5.1 +0 -0
  30. {multissh3-6.39 → multissh3-6.40}/tests/shells/Dockerfile.bash5.3 +0 -0
  31. {multissh3-6.39 → multissh3-6.40}/tests/shells/Dockerfile.centos7 +0 -0
  32. {multissh3-6.39 → multissh3-6.40}/tests/shells/Dockerfile.current +0 -0
  33. {multissh3-6.39 → multissh3-6.40}/tests/shells/Dockerfile.legacy +0 -0
  34. {multissh3-6.39 → multissh3-6.40}/tests/shells/Dockerfile.modern +0 -0
  35. {multissh3-6.39 → multissh3-6.40}/tests/test_cli_branches.py +0 -0
  36. {multissh3-6.39 → multissh3-6.40}/tests/test_cli_option_words.py +0 -0
  37. {multissh3-6.39 → multissh3-6.40}/tests/test_cli_processing.py +0 -0
  38. {multissh3-6.39 → multissh3-6.40}/tests/test_closed_output.py +0 -0
  39. {multissh3-6.39 → multissh3-6.40}/tests/test_color_helpers.py +0 -0
  40. {multissh3-6.39 → multissh3-6.40}/tests/test_command_terminal.py +0 -0
  41. {multissh3-6.39 → multissh3-6.40}/tests/test_compact_hostnames.py +0 -0
  42. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_cli.py +0 -0
  43. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_context.py +0 -0
  44. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_diagnose.py +0 -0
  45. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_editor.py +0 -0
  46. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_eval.py +0 -0
  47. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_hosts.py +0 -0
  48. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_hoststate.py +0 -0
  49. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_index.py +0 -0
  50. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_lex.py +0 -0
  51. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_mssh_helpers.py +0 -0
  52. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_password_spellings.py +0 -0
  53. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_picker.py +0 -0
  54. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_probe.py +0 -0
  55. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_start.py +0 -0
  56. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_store.py +0 -0
  57. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_suggest.py +0 -0
  58. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_terminal.py +0 -0
  59. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_untrusted.py +0 -0
  60. {multissh3-6.39 → multissh3-6.40}/tests/test_complete_zsh_options.py +0 -0
  61. {multissh3-6.39 → multissh3-6.40}/tests/test_config_validate.py +0 -0
  62. {multissh3-6.39 → multissh3-6.40}/tests/test_config_values.py +0 -0
  63. {multissh3-6.39 → multissh3-6.40}/tests/test_copy_id.py +0 -0
  64. {multissh3-6.39 → multissh3-6.40}/tests/test_curses_branches.py +0 -0
  65. {multissh3-6.39 → multissh3-6.40}/tests/test_curses_event_loop.py +0 -0
  66. {multissh3-6.39 → multissh3-6.40}/tests/test_curses_tui.py +0 -0
  67. {multissh3-6.39 → multissh3-6.40}/tests/test_curses_wide_chars.py +0 -0
  68. {multissh3-6.39 → multissh3-6.40}/tests/test_expand_hostnames.py +0 -0
  69. {multissh3-6.39 → multissh3-6.40}/tests/test_file_trust.py +0 -0
  70. {multissh3-6.39 → multissh3-6.40}/tests/test_harness_smoke.py +0 -0
  71. {multissh3-6.39 → multissh3-6.40}/tests/test_helpers_extra.py +0 -0
  72. {multissh3-6.39 → multissh3-6.40}/tests/test_history_replay.py +0 -0
  73. {multissh3-6.39 → multissh3-6.40}/tests/test_host.py +0 -0
  74. {multissh3-6.39 → multissh3-6.40}/tests/test_host_validity.py +0 -0
  75. {multissh3-6.39 → multissh3-6.40}/tests/test_hosts_file.py +0 -0
  76. {multissh3-6.39 → multissh3-6.40}/tests/test_identities_only.py +0 -0
  77. {multissh3-6.39 → multissh3-6.40}/tests/test_ipmi_lockout_wait.py +0 -0
  78. {multissh3-6.39 → multissh3-6.40}/tests/test_ipmi_logins.py +0 -0
  79. {multissh3-6.39 → multissh3-6.40}/tests/test_key_path_home.py +0 -0
  80. {multissh3-6.39 → multissh3-6.40}/tests/test_login_drop_retry.py +0 -0
  81. {multissh3-6.39 → multissh3-6.40}/tests/test_main_lifecycle.py +0 -0
  82. {multissh3-6.39 → multissh3-6.40}/tests/test_narrow_terminal.py +0 -0
  83. {multissh3-6.39 → multissh3-6.40}/tests/test_no_hosts.py +0 -0
  84. {multissh3-6.39 → multissh3-6.40}/tests/test_option_no_effect.py +0 -0
  85. {multissh3-6.39 → multissh3-6.40}/tests/test_options_after_command.py +0 -0
  86. {multissh3-6.39 → multissh3-6.40}/tests/test_output_merge.py +0 -0
  87. {multissh3-6.39 → multissh3-6.40}/tests/test_output_robustness.py +0 -0
  88. {multissh3-6.39 → multissh3-6.40}/tests/test_packaging.py +0 -0
  89. {multissh3-6.39 → multissh3-6.40}/tests/test_password_handling.py +0 -0
  90. {multissh3-6.39 → multissh3-6.40}/tests/test_passwords_out_of_ps.py +0 -0
  91. {multissh3-6.39 → multissh3-6.40}/tests/test_run_command.py +0 -0
  92. {multissh3-6.39 → multissh3-6.40}/tests/test_run_command_branches.py +0 -0
  93. {multissh3-6.39 → multissh3-6.40}/tests/test_run_command_characterization.py +0 -0
  94. {multissh3-6.39 → multissh3-6.40}/tests/test_run_command_exit.py +0 -0
  95. {multissh3-6.39 → multissh3-6.40}/tests/test_run_on_hosts.py +0 -0
  96. {multissh3-6.39 → multissh3-6.40}/tests/test_run_on_hosts_modes.py +0 -0
  97. {multissh3-6.39 → multissh3-6.40}/tests/test_runtime_branch_matrix.py +0 -0
  98. {multissh3-6.39 → multissh3-6.40}/tests/test_runtime_dir.py +0 -0
  99. {multissh3-6.39 → multissh3-6.40}/tests/test_shared_lines.py +0 -0
  100. {multissh3-6.39 → multissh3-6.40}/tests/test_smoke_optional.py +0 -0
  101. {multissh3-6.39 → multissh3-6.40}/tests/test_ssh_error_text.py +0 -0
  102. {multissh3-6.39 → multissh3-6.40}/tests/test_ssh_timeouts.py +0 -0
  103. {multissh3-6.39 → multissh3-6.40}/tests/test_sshpass_exit.py +0 -0
  104. {multissh3-6.39 → multissh3-6.40}/tests/test_start_run_on_hosts.py +0 -0
  105. {multissh3-6.39 → multissh3-6.40}/tests/test_stopped_commands.py +0 -0
  106. {multissh3-6.39 → multissh3-6.40}/tests/test_stream_reader.py +0 -0
  107. {multissh3-6.39 → multissh3-6.40}/tests/test_streamed_output.py +0 -0
  108. {multissh3-6.39 → multissh3-6.40}/tests/test_tui_ssh_notices.py +0 -0
  109. {multissh3-6.39 → multissh3-6.40}/tests/test_unavailable_hosts.py +0 -0
  110. {multissh3-6.39 → multissh3-6.40}/tests/test_underscore_settings.py +0 -0
  111. {multissh3-6.39 → multissh3-6.40}/tests/test_waiting.py +0 -0
  112. {multissh3-6.39 → multissh3-6.40}/tests/test_wide_wrap.py +0 -0
  113. {multissh3-6.39 → multissh3-6.40}/tests/vt_grid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 6.39
3
+ Version: 6.40
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -180,7 +180,7 @@ Defaults are read from the following chain map stored as json files, top ones ov
180
180
  >
181
181
  > `--store_config_file` writes the file readable by you only (`0600`), as it can hold passwords. The `/etc` locations above are written `0644` so every user can read them. An existing file's permissions are only ever tightened, and a file whose permissions cannot be restricted is left untouched (the config is printed instead).
182
182
  >
183
- > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link. Under `sudo`, root follows only its own links: with the default `b` the link is moved to the `.bak` and replaced by a regular file; with `o` the config is printed and the link is kept ([limitations](docs/limitations.md#under-sudo---store_config_file-replaces-a-symlinked-config-instead-of-writing-through-it)).
183
+ > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link. Under `sudo`, a config of the user who ran sudo (their file, their symlink, or a new one in a directory of theirs) is looked at, backed up and written with that user's rights: their link is written through where they may write the file it points to, the `.bak` and a new config are theirs, and a home on NFS that squashes root works. Where that user may not write, the config is printed instead. A link of anyone else (but root) is not followed. Under `sudo -E`, mssh runs as root and does not load your own `~/.multiSSH3.config.json` (a file of another user), so `--store_config_file` writes the system configs' settings plus the command line, and your earlier settings are left in the `.bak` ([limitations](docs/limitations.md#under-sudo--e-mssh-ignores-the-users-own-config)).
184
184
  >
185
185
  > The file is replaced in one step: a temporary file beside it, with the old file's owner and group where they can be kept, is renamed over it, so with `o` a full disk leaves the old one; with the default `b` the old one is the `.bak`. With `o` (overwrite), and for a config reached through a symlink, a file of another user is written only when its owner can be kept (as root); otherwise the config is printed instead, also when you may write the file through an ACL ([limitations](docs/limitations.md#another-users-config-shared-through-an-acl-is-not-written)). With the default `b`, a config that is not a symlink is moved to the `.bak` and the new one keeps its owner and group where that can be kept (the owner only as root; the group as anyone, for a file of yours); otherwise it is yours.
186
186
  >
@@ -261,6 +261,8 @@ The block applies to every ssh you run, not only mssh's. ssh fails outright (exi
261
261
 
262
262
  The sockets never go in your home directory: on clusters it is often NFS, GPFS or Lustre, where unix sockets may not work. The checks run in `/bin/sh` whatever your login shell (csh and fish too) and work with OpenSSH 7.4 and later. ssh runs each `Match exec` with your `$SHELL -c`, twice per ssh: that adds about 2 ms with bash, more with shells that read startup files for `-c` (tcsh reads `.cshrc` and `/etc/profile.d/*.csh`, fish its `config.fish`), and with a `$SHELL` that is not an executable every ssh fails. The checks also run when the autocomplete's background checker calls `ssh -G`; they only look at local directories (and may create `/dev/shm/multiSSH3-<uid>`).
263
263
 
264
+ Under `sudo`, a `~/.ssh` and `~/.ssh/config` of the user who ran sudo are written with their rights, and a new `~/.ssh` is theirs. A `~/.ssh/config` that is a symlink of anyone else (but root, or that user under sudo) is not followed: mssh prints `Error adding ControlMaster configuration: ... is a symlink owned by someone else, not writing through it` and changes nothing.
265
+
264
266
  Up to multiSSH3 6.31 the block was `Host *` with `ControlPath /run/user/%i/ssh_sockets_%C`, which makes every ssh fail where `/run/user/<uid>` is missing. Run `mssh --add_control_master_config` again: it replaces that block, and only it, keeping the rest of the file and its permissions as they were. Running it once more changes nothing.
265
267
 
266
268
  ### IPMI Logins
@@ -498,7 +500,7 @@ A new block is appended to your rc file; changing or removing it replaces the fi
498
500
 
499
501
  - **Hosts:** host strings you typed before, env file aliases, `/etc/hosts`, ssh config `Host` entries, `known_hosts` and the ARP table. Large clusters are offered as ranges (`node[0001-2000]`) until what you typed narrows them down. Hosts mssh recently found unreachable come last (with the user this line gives them: `root@node05` and `node05` are told apart).
500
502
  - **Options and values:** option names, and values you used before (`-u`, `-t`, ...). Paths complete as files. Passwords are never completed or shown, the ones in your config file included.
501
- - **Commands:** commands from your mssh history, the rest of a command from this machine's own completion (`mssh grp systemctl rest<TAB>`; in bash as bash-completion completes `sudo CMD`, with a few gaps in [limitations](docs/limitations.md#autocomplete)), and the magic strings (`#HOST#`, ...). A command typed as separate words gets the next words of commands you ran, with the quotes they ran with (`'ssh*'` comes as `\'ssh\*\'`, so the remote shell gets the same command), never a word holding a space: mssh runs each word as its own command when one of them holds a space, so type such a command in quotes (`mssh grp "grep 'connection refused' /var/log/syslog"`).
503
+ - **Commands:** commands from your mssh history, the rest of a command from this machine's own completion (`mssh grp systemctl rest<TAB>`; in bash as bash-completion completes `sudo CMD`, with a few gaps in [limitations](docs/limitations.md#autocomplete)), and the magic strings (`#HOST#`, ...). A command typed as separate words gets the next words of commands you ran, with the quotes they ran with (`'ssh*'` comes as `\'ssh\*\'`, so the remote shell gets the same command), never a word holding a space: mssh runs each word as its own command when one of them holds a space, so type such a command in quotes (`mssh grp "grep 'connection refused' /var/log/syslog"`). A local file name holding whitespace or a shell character goes in quoted for this shell, which is wrong for the remote command (`my\ file` runs `my file` as a command of its own, `a\&b` makes the remote shell run `b`), and TAB inside a quoted argument (`mssh grp cat 'my f<TAB>`) offers nothing useful; both are to be fixed after 6.40 ([limitations](docs/limitations.md#local-file-names-with-whitespace-or-shell-characters-are-wrong-in-a-remote-command), [limitations](docs/limitations.md#inside-a-quoted-argument-the-engine-completes-a-quoted-command)).
502
504
 
503
505
  Ranking uses the rest of the line on both sides of the cursor: after `mssh gpu[01-16] "` the commands you run on those hosts come first, and in the hosts of `mssh ▮ "systemctl restart slurmd"` the hosts you restarted slurmd on come first.
504
506
 
@@ -570,7 +572,11 @@ Hosts are checked in the background by `mssh-complete probe`: DNS, `ssh -G` (whi
570
572
 
571
573
  > In plain bash (without ble.sh), Ctrl-R and the line editor take their keys at the first prompt, through `PROMPT_COMMAND`. Where `PROMPT_COMMAND` is read-only (some audit setups on shared clusters make it so), they are left out without a message; TAB completion still works.
572
574
 
573
- > The bash glue runs with `nocasematch` off and gives you your setting back when it returns; with `shopt -s nocasematch` on, a Ctrl-C during a TAB can leave it off ([limitations](docs/limitations.md#with-nocasematch-on-in-bash-a-ctrl-c-during-a-tab-can-leave-it-off)). Under `bind TAB:menu-complete`, mssh's words beside the words of a command like git (which asks for no space) go in without a space ([limitations](docs/limitations.md#under-menu-complete-msshs-words-beside-a-nospace-commands-mixed-words-get-no-space)).
575
+ > The bash glue runs with `nocasematch` off and gives you your setting back when it returns; with `shopt -s nocasematch` on, a Ctrl-C during a TAB can leave it off ([limitations](docs/limitations.md#with-nocasematch-on-in-bash-a-ctrl-c-during-a-tab-can-leave-it-off)). Under `bind TAB:menu-complete`, and picked from ble.sh's menu, mssh's words beside the words of a command like git (which asks for no space, `--format=` beside `--oneline `) go in without a space ([limitations](docs/limitations.md#under-menu-complete-or-bleshs-menu-msshs-words-beside-a-nospace-commands-mixed-words-get-no-space)). Where python-argcomplete's global completer (`complete -D`) is set, a remote command with no completion file completes local file names, as with bash-completion alone: mssh never runs a local program named like the remote command ([limitations](docs/limitations.md#bash-a-remote-command-with-no-completion-file-completes-local-file-names-where-python-argcompletes-global-completer-is-set)).
576
+
577
+ > With ble.sh, one of mssh's remote words (from its history) that names a directory on this machine goes in with `/` and no space ([limitations](docs/limitations.md#blesh-a-remote-word-of-msshs-that-names-a-local-directory-gets-a--and-no-space)), and a remote `~/...` path from mssh's history goes in with a bare `~`, which this shell expands to your local home ([limitations](docs/limitations.md#blesh-a-remote--path-from-msshs-history-goes-in-with-a-bare-)); plain bash keeps the space and inserts `\~`.
578
+
579
+ > With ble.sh, a word that ble.sh splits at an `=` or `:` after a quote, a backslash or an expansion (`"a"=b`, `$V:x`) is read as in 6.39; a `~` after such a split comes back as this machine's home ([limitations](docs/limitations.md#blesh-a-word-split-at-an--or--after-a-quote-backslash-or-expansion-is-read-as-in-639)). On bash 4.3 and 4.4, a remote command's `-o noquote` is not seen, so the local file names completed for it are quoted ([limitations](docs/limitations.md#bash-43-and-44-a-remote-commands--o-noquote-is-not-seen)). With zsh before 5.8, a Ctrl-C during a completion query now and then reaches the query instead of the shell, as in 6.39; a fix is planned for the change after 6.40 ([limitations](docs/limitations.md#zsh-before-58-a-ctrl-c-during-a-completion-query-can-reach-the-query)).
574
580
 
575
581
  ## Options
576
582
 
@@ -978,7 +984,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
978
984
 
979
985
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
980
986
 
981
- Below is a sample help message output from multiSSH3 6.39 @ 2026-10-29 (no config file, 120 columns; the lines after the options list the programs found on that machine)
987
+ Below is a sample help message output from multiSSH3 6.40 @ 2026-11-01 (no config file, 120 columns; the lines after the options list the programs found on that machine)
982
988
 
983
989
  ```bash
984
990
  $ mssh -h
@@ -1380,6 +1386,8 @@ __DEBUG_MODE = False
1380
1386
 
1381
1387
  > `run_command_on_hosts` reads `_DEFAULT_CALLED`, `_DEFAULT_RETURN_UNFINISHED`, `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` and `_DEFAULT_NO_START` each time it is called, so setting them after `import multiSSH3`, or in a file given with `--config_file`, works. The `mssh` command always starts its hosts and waits for them: of these four, only `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` changes what it does.
1382
1388
 
1389
+ > Under sudo, `main()` with `--store_config_file` or `--add_control_master_config` switches the whole process's effective user to the user who ran sudo while it writes their config, so a program's other threads run as that user for that moment ([limitations](docs/limitations.md#under-sudo-a-program-calling-main-runs-its-threads-as-the-user-while-a-config-is-written)).
1390
+
1383
1391
  > `_etc_hosts` is a custom dict to use instead of reading /etc/hosts file for hostname resolution.
1384
1392
 
1385
1393
  > `ERROR_MESSAGES_TO_IGNORE` lists regexes for ssh's own notices, which mssh leaves out of each host's stderr (also in `--json` and `--greppable` output). Each regex is searched anywhere in every stderr line, so anchor yours with `^` and `$` to match whole lines (in the JSON config file, write a regex backslash as `\\`). The defaults are anchored to the lines OpenSSH prints, so a remote program's error that uses the same words, such as curl's `Recv failure: Connection reset by peer`, is kept. Connection resets are not hidden: `kex_exchange_identification: read: Connection reset by peer` is how sshd's `MaxStartups` turns away connections opened at once. Config files that `--store_config_file` or `--generate_config_file` wrote before 6.20 hold a copy of the old, unanchored defaults; mssh ignores such an unchanged copy and uses the current defaults.
@@ -1390,6 +1398,35 @@ __DEBUG_MODE = False
1390
1398
 
1391
1399
  > `__DEBUG_MODE` is used to control whether to print host thread execution debug information to its stderr.
1392
1400
 
1401
+ ## Upgrading to 6.40 from 6.39
1402
+
1403
+ Fixes for the six items deferred from 6.39 (bash and ble.sh completion, `--store_config_file` under sudo) and for three found beside them. Run `mssh --completion install` again in bash, with or without ble.sh, to get the new glue: the completion fixes below are in it (zsh's and fish's glues did not change). What you may notice:
1404
+
1405
+ - Fix (bash): `mssh h CMD --opt=<TAB>`, where CMD's completion offers `--opt=`, no longer adds a space after the `=`; beside other words the part they share goes in (`--opt=a`).
1406
+ - Fix (bash): a remote command's `-W` list, `-G` pattern and actions (`-u`, `-d`, `-f`, ...) complete the word after a `:` or `=` as bash does, when the whole word matches nothing: `mssh h CMD --user=roo<TAB>` with `complete -u CMD` gives `--user=root`, `x=zz` with `complete -d` gives `x=zzsub/`, and `--x=al` with `-W "alpha beta"` gives `--x=alpha`. A file name or a `-W` word holding a `:` or `=` still completes as one word.
1407
+ - Fix (bash): local file names completed for a remote command (its command word, `-f`, `-d`, `-G`, a command with no completion file) are quoted and marked as bash does: `zzmy\ file`, `zzamp\&b`, `zzdir/`, not `zzmy file` (two words here), `zzamp&b` (this shell ran `b`) or `zzdir `. With mssh's own words beside them, also beside bash-completion's `_filedir` (which `cat`, `ls`, `cp`, ... run), a remote word that names a local directory keeps its space, and a remote `~/...` from mssh's history keeps its `\~`; the list then shows the names with their directories (`/etc/sysctl.d/`), not just their last part. A spec with `-o nospace -o filenames` that offers nothing now leaves mssh's own word its space.
1408
+ - Fix (ble.sh): TAB inside a quoted remote command completes, from the command's completion and from mssh's history (`mssh h "systemctl rest<TAB>`, also with a `=` in it); `mssh h cat ~/zzn<TAB>` offers the path from mssh's history, with the `~` as typed; an earlier argument holding a backslash (`zz\ a`) no longer makes the word being typed a command name.
1409
+ - Fix (ble.sh): beside a git-style command (which asks for no space) TAB in the middle of a line leaves one space, not two; mssh's words get their space beside a command whose words all end in one (git's branches, scp's file names); a word both the command and mssh's history offer is listed once; scp's escaped file names go in as they do natively. Beside mixed words (`--format=` with `--oneline `), mssh's words picked from the menu still get no space, and a word of mssh's and one of the command's that differ only in case are both listed, as readline lists them (6.39 folded case there).
1410
+ - Under sudo (root with `SUDO_UID`), the config of the user who ran sudo is written with their rights: `sudo -E mssh --store_config_file` on a config that is their symlink into a dotfiles repository writes through it and keeps the link (6.39 moved the link to the `.bak` and put a regular file in its place); the `.bak` and a new config are theirs (6.39 made a new config root's `0600` file, which they could not read), and an NFS home that squashes root works. Where that user may not write (the file the link points to, the directory), the write fails and the config is printed, as when they run mssh themselves. A config of anyone else, and every config of root without sudo, are as in 6.39.
1411
+ - `--add_control_master_config` follows the same rules for `~/.ssh/config`: under sudo the user's `~/.ssh` and its config are written with their rights, and a new `~/.ssh` or config is theirs (6.39 made a new `~/.ssh` root's `0700`, which they cannot enter, so their ssh silently ignored the config in it; a root-owned config inside their own `~/.ssh` is accepted by ssh); a `~/.ssh/config` that is a symlink of anyone else (but root) is not followed, with a one-line error and no traceback (6.39, as root, wrote the block wherever such a link pointed).
1412
+ - Fix: with the default answer `b`, a config that is someone else's symlink (one planted in `/tmp`) is moved to the `.bak` and the new file written in its place is the writer's, `0600`; 6.39, as root, gave it the owner of the file the link pointed to, though it can hold passwords.
1413
+ - Fix: root writing a file in a directory another user owns (sudo keeping HOME, or `--store_config_file PATH` there) gives the temporary file its owner on the open file, so that user can no longer swap it for a link between the write and the `chown` and have root give them a file of their choice.
1414
+ - After `--store_config_file`, the config shown is what mssh wrote, from memory, not read back from the file (root may not read it on an NFS home that squashes root).
1415
+ - New known limitations and deferrals, each in [docs/limitations.md](docs/limitations.md):
1416
+ - ble.sh: mssh's words picked from ble.sh's menu beside a nospace command's mixed words get no space (the menu-complete entry, extended);
1417
+ - bash: where python-argcomplete's global completer (`complete -D`) is set, a remote command with no completion file completes local file names;
1418
+ - ble.sh: a remote word of mssh's that names a local directory gets `/` and no space;
1419
+ - ble.sh: a remote `~/...` from mssh's history goes in with a bare `~`, which this shell expands;
1420
+ - under `sudo -E`, mssh ignores your own config, so `--store_config_file` there keeps your earlier settings only in the `.bak`;
1421
+ - ble.sh: a word split at an `=` or `:` after a quote, a backslash or an expansion is read as in 6.39, and a `~` after such a split comes back as this machine's home;
1422
+ - bash 4.3 and 4.4: a remote command's `-o noquote` is not seen;
1423
+ - under sudo, a program calling `main()` with `--store_config_file` or `--add_control_master_config` runs its other threads as the user while the config is written;
1424
+ - deferred to the change after 6.40: local file names with whitespace or shell characters in a remote command (all shells), TAB inside a quoted argument (`mssh h cat 'my f<TAB>`), and, with zsh before 5.8, a Ctrl-C during a completion query that now and then reaches the query (as in 6.39).
1425
+ - Changed for programs and scripts:
1426
+ - The bash glue under ble.sh sends the word as typed, and the engine reads the word of a `bash` request with `ble=1` as typed (lexed, as zsh's and fish's); a 6.39 glue's word is read right too.
1427
+ - The bash glue no longer reads a `C` record's fourth field (the unescaped text); the engine still sends it for older glues.
1428
+ - Under sudo, `_own_symlink` and `_replace_file` compare with the effective uid, not the real one.
1429
+
1393
1430
  ## Upgrading to 6.39 from 6.38
1394
1431
 
1395
1432
  A simplification pass: multiSSH3.py is about 740 lines shorter, mssh_complete.py about 1,470 (the history replay moved to `tests/replay.py`). What you may notice:
@@ -1400,7 +1437,7 @@ A simplification pass: multiSSH3.py is about 740 lines shorter, mssh_complete.py
1400
1437
  - Hosts whose names do not compact into one range are listed in natural order (`db2,db10`), not sorted as text (`db10,db2`), in output headers, merged groups and `Skipping hosts:`.
1401
1438
  - Fix: a message mssh cannot write to stderr is dropped. 6.38 wrote it to stdout, so with `PYTHONUNBUFFERED=1` `mssh ... 2>&1 | true` exited 1 without running anything, and `-j` output could hold a stray line.
1402
1439
  - `--store_config_file`, its `.bak` and `--add_control_master_config` write through one atomic writer: root writing a user's config keeps its owner and cannot leave it half written, and the new config keeps the old one's group with the default answer `b` too, where it can be kept (6.38 gave it yours).
1403
- - Under `-11` with `--error_only`, `--script` or `-Q`, the `Skipping unavailable host` line is no longer printed. A `__multiSSH3_UNAVAILABLE_HOSTS` key in an env file (written by 4.89) is no longer read.
1440
+ - Under `-11` with `--error_only`, `--script` or `-Q`, the `Skipping unavailable host` line is no longer printed. A `__multiSSH3_UNAVAILABLE_HOSTS` line in an env file, which 6.38 read when it had no unavailable-hosts cache yet, is no longer read.
1404
1441
  - Autocomplete: the bash completion of a remote command works as bash-completion completes `sudo CMD`; warnings skip hosts words the shell rewrites; history rows written by 6.26 and older are inserted with their commands in single quotes, so the local shell expands no `!`, `$` or backtick in them; the debug log (`MSSH_COMPLETE_DEBUG=1`) no longer holds the line typed in the editor.
1405
1442
  - Fix (zsh): `mssh HOSTS CMD ...<TAB>` no longer adds local file names beside the remote command's words when python-argcomplete (`#compdef -P *`) is installed.
1406
1443
  - New known limitations, each in [docs/limitations.md](docs/limitations.md):
@@ -154,7 +154,7 @@ Defaults are read from the following chain map stored as json files, top ones ov
154
154
  >
155
155
  > `--store_config_file` writes the file readable by you only (`0600`), as it can hold passwords. The `/etc` locations above are written `0644` so every user can read them. An existing file's permissions are only ever tightened, and a file whose permissions cannot be restricted is left untouched (the config is printed instead).
156
156
  >
157
- > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link. Under `sudo`, root follows only its own links: with the default `b` the link is moved to the `.bak` and replaced by a regular file; with `o` the config is printed and the link is kept ([limitations](docs/limitations.md#under-sudo---store_config_file-replaces-a-symlinked-config-instead-of-writing-through-it)).
157
+ > A config file that is a symlink (e.g. into a dotfiles repository) is written through the link; its backup (`.bak`, the default answer when the file exists) is a copy next to the link. Under `sudo`, a config of the user who ran sudo (their file, their symlink, or a new one in a directory of theirs) is looked at, backed up and written with that user's rights: their link is written through where they may write the file it points to, the `.bak` and a new config are theirs, and a home on NFS that squashes root works. Where that user may not write, the config is printed instead. A link of anyone else (but root) is not followed. Under `sudo -E`, mssh runs as root and does not load your own `~/.multiSSH3.config.json` (a file of another user), so `--store_config_file` writes the system configs' settings plus the command line, and your earlier settings are left in the `.bak` ([limitations](docs/limitations.md#under-sudo--e-mssh-ignores-the-users-own-config)).
158
158
  >
159
159
  > The file is replaced in one step: a temporary file beside it, with the old file's owner and group where they can be kept, is renamed over it, so with `o` a full disk leaves the old one; with the default `b` the old one is the `.bak`. With `o` (overwrite), and for a config reached through a symlink, a file of another user is written only when its owner can be kept (as root); otherwise the config is printed instead, also when you may write the file through an ACL ([limitations](docs/limitations.md#another-users-config-shared-through-an-acl-is-not-written)). With the default `b`, a config that is not a symlink is moved to the `.bak` and the new one keeps its owner and group where that can be kept (the owner only as root; the group as anyone, for a file of yours); otherwise it is yours.
160
160
  >
@@ -235,6 +235,8 @@ The block applies to every ssh you run, not only mssh's. ssh fails outright (exi
235
235
 
236
236
  The sockets never go in your home directory: on clusters it is often NFS, GPFS or Lustre, where unix sockets may not work. The checks run in `/bin/sh` whatever your login shell (csh and fish too) and work with OpenSSH 7.4 and later. ssh runs each `Match exec` with your `$SHELL -c`, twice per ssh: that adds about 2 ms with bash, more with shells that read startup files for `-c` (tcsh reads `.cshrc` and `/etc/profile.d/*.csh`, fish its `config.fish`), and with a `$SHELL` that is not an executable every ssh fails. The checks also run when the autocomplete's background checker calls `ssh -G`; they only look at local directories (and may create `/dev/shm/multiSSH3-<uid>`).
237
237
 
238
+ Under `sudo`, a `~/.ssh` and `~/.ssh/config` of the user who ran sudo are written with their rights, and a new `~/.ssh` is theirs. A `~/.ssh/config` that is a symlink of anyone else (but root, or that user under sudo) is not followed: mssh prints `Error adding ControlMaster configuration: ... is a symlink owned by someone else, not writing through it` and changes nothing.
239
+
238
240
  Up to multiSSH3 6.31 the block was `Host *` with `ControlPath /run/user/%i/ssh_sockets_%C`, which makes every ssh fail where `/run/user/<uid>` is missing. Run `mssh --add_control_master_config` again: it replaces that block, and only it, keeping the rest of the file and its permissions as they were. Running it once more changes nothing.
239
241
 
240
242
  ### IPMI Logins
@@ -472,7 +474,7 @@ A new block is appended to your rc file; changing or removing it replaces the fi
472
474
 
473
475
  - **Hosts:** host strings you typed before, env file aliases, `/etc/hosts`, ssh config `Host` entries, `known_hosts` and the ARP table. Large clusters are offered as ranges (`node[0001-2000]`) until what you typed narrows them down. Hosts mssh recently found unreachable come last (with the user this line gives them: `root@node05` and `node05` are told apart).
474
476
  - **Options and values:** option names, and values you used before (`-u`, `-t`, ...). Paths complete as files. Passwords are never completed or shown, the ones in your config file included.
475
- - **Commands:** commands from your mssh history, the rest of a command from this machine's own completion (`mssh grp systemctl rest<TAB>`; in bash as bash-completion completes `sudo CMD`, with a few gaps in [limitations](docs/limitations.md#autocomplete)), and the magic strings (`#HOST#`, ...). A command typed as separate words gets the next words of commands you ran, with the quotes they ran with (`'ssh*'` comes as `\'ssh\*\'`, so the remote shell gets the same command), never a word holding a space: mssh runs each word as its own command when one of them holds a space, so type such a command in quotes (`mssh grp "grep 'connection refused' /var/log/syslog"`).
477
+ - **Commands:** commands from your mssh history, the rest of a command from this machine's own completion (`mssh grp systemctl rest<TAB>`; in bash as bash-completion completes `sudo CMD`, with a few gaps in [limitations](docs/limitations.md#autocomplete)), and the magic strings (`#HOST#`, ...). A command typed as separate words gets the next words of commands you ran, with the quotes they ran with (`'ssh*'` comes as `\'ssh\*\'`, so the remote shell gets the same command), never a word holding a space: mssh runs each word as its own command when one of them holds a space, so type such a command in quotes (`mssh grp "grep 'connection refused' /var/log/syslog"`). A local file name holding whitespace or a shell character goes in quoted for this shell, which is wrong for the remote command (`my\ file` runs `my file` as a command of its own, `a\&b` makes the remote shell run `b`), and TAB inside a quoted argument (`mssh grp cat 'my f<TAB>`) offers nothing useful; both are to be fixed after 6.40 ([limitations](docs/limitations.md#local-file-names-with-whitespace-or-shell-characters-are-wrong-in-a-remote-command), [limitations](docs/limitations.md#inside-a-quoted-argument-the-engine-completes-a-quoted-command)).
476
478
 
477
479
  Ranking uses the rest of the line on both sides of the cursor: after `mssh gpu[01-16] "` the commands you run on those hosts come first, and in the hosts of `mssh ▮ "systemctl restart slurmd"` the hosts you restarted slurmd on come first.
478
480
 
@@ -544,7 +546,11 @@ Hosts are checked in the background by `mssh-complete probe`: DNS, `ssh -G` (whi
544
546
 
545
547
  > In plain bash (without ble.sh), Ctrl-R and the line editor take their keys at the first prompt, through `PROMPT_COMMAND`. Where `PROMPT_COMMAND` is read-only (some audit setups on shared clusters make it so), they are left out without a message; TAB completion still works.
546
548
 
547
- > The bash glue runs with `nocasematch` off and gives you your setting back when it returns; with `shopt -s nocasematch` on, a Ctrl-C during a TAB can leave it off ([limitations](docs/limitations.md#with-nocasematch-on-in-bash-a-ctrl-c-during-a-tab-can-leave-it-off)). Under `bind TAB:menu-complete`, mssh's words beside the words of a command like git (which asks for no space) go in without a space ([limitations](docs/limitations.md#under-menu-complete-msshs-words-beside-a-nospace-commands-mixed-words-get-no-space)).
549
+ > The bash glue runs with `nocasematch` off and gives you your setting back when it returns; with `shopt -s nocasematch` on, a Ctrl-C during a TAB can leave it off ([limitations](docs/limitations.md#with-nocasematch-on-in-bash-a-ctrl-c-during-a-tab-can-leave-it-off)). Under `bind TAB:menu-complete`, and picked from ble.sh's menu, mssh's words beside the words of a command like git (which asks for no space, `--format=` beside `--oneline `) go in without a space ([limitations](docs/limitations.md#under-menu-complete-or-bleshs-menu-msshs-words-beside-a-nospace-commands-mixed-words-get-no-space)). Where python-argcomplete's global completer (`complete -D`) is set, a remote command with no completion file completes local file names, as with bash-completion alone: mssh never runs a local program named like the remote command ([limitations](docs/limitations.md#bash-a-remote-command-with-no-completion-file-completes-local-file-names-where-python-argcompletes-global-completer-is-set)).
550
+
551
+ > With ble.sh, one of mssh's remote words (from its history) that names a directory on this machine goes in with `/` and no space ([limitations](docs/limitations.md#blesh-a-remote-word-of-msshs-that-names-a-local-directory-gets-a--and-no-space)), and a remote `~/...` path from mssh's history goes in with a bare `~`, which this shell expands to your local home ([limitations](docs/limitations.md#blesh-a-remote--path-from-msshs-history-goes-in-with-a-bare-)); plain bash keeps the space and inserts `\~`.
552
+
553
+ > With ble.sh, a word that ble.sh splits at an `=` or `:` after a quote, a backslash or an expansion (`"a"=b`, `$V:x`) is read as in 6.39; a `~` after such a split comes back as this machine's home ([limitations](docs/limitations.md#blesh-a-word-split-at-an--or--after-a-quote-backslash-or-expansion-is-read-as-in-639)). On bash 4.3 and 4.4, a remote command's `-o noquote` is not seen, so the local file names completed for it are quoted ([limitations](docs/limitations.md#bash-43-and-44-a-remote-commands--o-noquote-is-not-seen)). With zsh before 5.8, a Ctrl-C during a completion query now and then reaches the query instead of the shell, as in 6.39; a fix is planned for the change after 6.40 ([limitations](docs/limitations.md#zsh-before-58-a-ctrl-c-during-a-completion-query-can-reach-the-query)).
548
554
 
549
555
  ## Options
550
556
 
@@ -952,7 +958,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
952
958
 
953
959
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
954
960
 
955
- Below is a sample help message output from multiSSH3 6.39 @ 2026-10-29 (no config file, 120 columns; the lines after the options list the programs found on that machine)
961
+ Below is a sample help message output from multiSSH3 6.40 @ 2026-11-01 (no config file, 120 columns; the lines after the options list the programs found on that machine)
956
962
 
957
963
  ```bash
958
964
  $ mssh -h
@@ -1354,6 +1360,8 @@ __DEBUG_MODE = False
1354
1360
 
1355
1361
  > `run_command_on_hosts` reads `_DEFAULT_CALLED`, `_DEFAULT_RETURN_UNFINISHED`, `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` and `_DEFAULT_NO_START` each time it is called, so setting them after `import multiSSH3`, or in a file given with `--config_file`, works. The `mssh` command always starts its hosts and waits for them: of these four, only `_DEFAULT_UPDATE_UNREACHABLE_HOSTS` changes what it does.
1356
1362
 
1363
+ > Under sudo, `main()` with `--store_config_file` or `--add_control_master_config` switches the whole process's effective user to the user who ran sudo while it writes their config, so a program's other threads run as that user for that moment ([limitations](docs/limitations.md#under-sudo-a-program-calling-main-runs-its-threads-as-the-user-while-a-config-is-written)).
1364
+
1357
1365
  > `_etc_hosts` is a custom dict to use instead of reading /etc/hosts file for hostname resolution.
1358
1366
 
1359
1367
  > `ERROR_MESSAGES_TO_IGNORE` lists regexes for ssh's own notices, which mssh leaves out of each host's stderr (also in `--json` and `--greppable` output). Each regex is searched anywhere in every stderr line, so anchor yours with `^` and `$` to match whole lines (in the JSON config file, write a regex backslash as `\\`). The defaults are anchored to the lines OpenSSH prints, so a remote program's error that uses the same words, such as curl's `Recv failure: Connection reset by peer`, is kept. Connection resets are not hidden: `kex_exchange_identification: read: Connection reset by peer` is how sshd's `MaxStartups` turns away connections opened at once. Config files that `--store_config_file` or `--generate_config_file` wrote before 6.20 hold a copy of the old, unanchored defaults; mssh ignores such an unchanged copy and uses the current defaults.
@@ -1364,6 +1372,35 @@ __DEBUG_MODE = False
1364
1372
 
1365
1373
  > `__DEBUG_MODE` is used to control whether to print host thread execution debug information to its stderr.
1366
1374
 
1375
+ ## Upgrading to 6.40 from 6.39
1376
+
1377
+ Fixes for the six items deferred from 6.39 (bash and ble.sh completion, `--store_config_file` under sudo) and for three found beside them. Run `mssh --completion install` again in bash, with or without ble.sh, to get the new glue: the completion fixes below are in it (zsh's and fish's glues did not change). What you may notice:
1378
+
1379
+ - Fix (bash): `mssh h CMD --opt=<TAB>`, where CMD's completion offers `--opt=`, no longer adds a space after the `=`; beside other words the part they share goes in (`--opt=a`).
1380
+ - Fix (bash): a remote command's `-W` list, `-G` pattern and actions (`-u`, `-d`, `-f`, ...) complete the word after a `:` or `=` as bash does, when the whole word matches nothing: `mssh h CMD --user=roo<TAB>` with `complete -u CMD` gives `--user=root`, `x=zz` with `complete -d` gives `x=zzsub/`, and `--x=al` with `-W "alpha beta"` gives `--x=alpha`. A file name or a `-W` word holding a `:` or `=` still completes as one word.
1381
+ - Fix (bash): local file names completed for a remote command (its command word, `-f`, `-d`, `-G`, a command with no completion file) are quoted and marked as bash does: `zzmy\ file`, `zzamp\&b`, `zzdir/`, not `zzmy file` (two words here), `zzamp&b` (this shell ran `b`) or `zzdir `. With mssh's own words beside them, also beside bash-completion's `_filedir` (which `cat`, `ls`, `cp`, ... run), a remote word that names a local directory keeps its space, and a remote `~/...` from mssh's history keeps its `\~`; the list then shows the names with their directories (`/etc/sysctl.d/`), not just their last part. A spec with `-o nospace -o filenames` that offers nothing now leaves mssh's own word its space.
1382
+ - Fix (ble.sh): TAB inside a quoted remote command completes, from the command's completion and from mssh's history (`mssh h "systemctl rest<TAB>`, also with a `=` in it); `mssh h cat ~/zzn<TAB>` offers the path from mssh's history, with the `~` as typed; an earlier argument holding a backslash (`zz\ a`) no longer makes the word being typed a command name.
1383
+ - Fix (ble.sh): beside a git-style command (which asks for no space) TAB in the middle of a line leaves one space, not two; mssh's words get their space beside a command whose words all end in one (git's branches, scp's file names); a word both the command and mssh's history offer is listed once; scp's escaped file names go in as they do natively. Beside mixed words (`--format=` with `--oneline `), mssh's words picked from the menu still get no space, and a word of mssh's and one of the command's that differ only in case are both listed, as readline lists them (6.39 folded case there).
1384
+ - Under sudo (root with `SUDO_UID`), the config of the user who ran sudo is written with their rights: `sudo -E mssh --store_config_file` on a config that is their symlink into a dotfiles repository writes through it and keeps the link (6.39 moved the link to the `.bak` and put a regular file in its place); the `.bak` and a new config are theirs (6.39 made a new config root's `0600` file, which they could not read), and an NFS home that squashes root works. Where that user may not write (the file the link points to, the directory), the write fails and the config is printed, as when they run mssh themselves. A config of anyone else, and every config of root without sudo, are as in 6.39.
1385
+ - `--add_control_master_config` follows the same rules for `~/.ssh/config`: under sudo the user's `~/.ssh` and its config are written with their rights, and a new `~/.ssh` or config is theirs (6.39 made a new `~/.ssh` root's `0700`, which they cannot enter, so their ssh silently ignored the config in it; a root-owned config inside their own `~/.ssh` is accepted by ssh); a `~/.ssh/config` that is a symlink of anyone else (but root) is not followed, with a one-line error and no traceback (6.39, as root, wrote the block wherever such a link pointed).
1386
+ - Fix: with the default answer `b`, a config that is someone else's symlink (one planted in `/tmp`) is moved to the `.bak` and the new file written in its place is the writer's, `0600`; 6.39, as root, gave it the owner of the file the link pointed to, though it can hold passwords.
1387
+ - Fix: root writing a file in a directory another user owns (sudo keeping HOME, or `--store_config_file PATH` there) gives the temporary file its owner on the open file, so that user can no longer swap it for a link between the write and the `chown` and have root give them a file of their choice.
1388
+ - After `--store_config_file`, the config shown is what mssh wrote, from memory, not read back from the file (root may not read it on an NFS home that squashes root).
1389
+ - New known limitations and deferrals, each in [docs/limitations.md](docs/limitations.md):
1390
+ - ble.sh: mssh's words picked from ble.sh's menu beside a nospace command's mixed words get no space (the menu-complete entry, extended);
1391
+ - bash: where python-argcomplete's global completer (`complete -D`) is set, a remote command with no completion file completes local file names;
1392
+ - ble.sh: a remote word of mssh's that names a local directory gets `/` and no space;
1393
+ - ble.sh: a remote `~/...` from mssh's history goes in with a bare `~`, which this shell expands;
1394
+ - under `sudo -E`, mssh ignores your own config, so `--store_config_file` there keeps your earlier settings only in the `.bak`;
1395
+ - ble.sh: a word split at an `=` or `:` after a quote, a backslash or an expansion is read as in 6.39, and a `~` after such a split comes back as this machine's home;
1396
+ - bash 4.3 and 4.4: a remote command's `-o noquote` is not seen;
1397
+ - under sudo, a program calling `main()` with `--store_config_file` or `--add_control_master_config` runs its other threads as the user while the config is written;
1398
+ - deferred to the change after 6.40: local file names with whitespace or shell characters in a remote command (all shells), TAB inside a quoted argument (`mssh h cat 'my f<TAB>`), and, with zsh before 5.8, a Ctrl-C during a completion query that now and then reaches the query (as in 6.39).
1399
+ - Changed for programs and scripts:
1400
+ - The bash glue under ble.sh sends the word as typed, and the engine reads the word of a `bash` request with `ble=1` as typed (lexed, as zsh's and fish's); a 6.39 glue's word is read right too.
1401
+ - The bash glue no longer reads a `C` record's fourth field (the unescaped text); the engine still sends it for older glues.
1402
+ - Under sudo, `_own_symlink` and `_replace_file` compare with the effective uid, not the real one.
1403
+
1367
1404
  ## Upgrading to 6.39 from 6.38
1368
1405
 
1369
1406
  A simplification pass: multiSSH3.py is about 740 lines shorter, mssh_complete.py about 1,470 (the history replay moved to `tests/replay.py`). What you may notice:
@@ -1374,7 +1411,7 @@ A simplification pass: multiSSH3.py is about 740 lines shorter, mssh_complete.py
1374
1411
  - Hosts whose names do not compact into one range are listed in natural order (`db2,db10`), not sorted as text (`db10,db2`), in output headers, merged groups and `Skipping hosts:`.
1375
1412
  - Fix: a message mssh cannot write to stderr is dropped. 6.38 wrote it to stdout, so with `PYTHONUNBUFFERED=1` `mssh ... 2>&1 | true` exited 1 without running anything, and `-j` output could hold a stray line.
1376
1413
  - `--store_config_file`, its `.bak` and `--add_control_master_config` write through one atomic writer: root writing a user's config keeps its owner and cannot leave it half written, and the new config keeps the old one's group with the default answer `b` too, where it can be kept (6.38 gave it yours).
1377
- - Under `-11` with `--error_only`, `--script` or `-Q`, the `Skipping unavailable host` line is no longer printed. A `__multiSSH3_UNAVAILABLE_HOSTS` key in an env file (written by 4.89) is no longer read.
1414
+ - Under `-11` with `--error_only`, `--script` or `-Q`, the `Skipping unavailable host` line is no longer printed. A `__multiSSH3_UNAVAILABLE_HOSTS` line in an env file, which 6.38 read when it had no unavailable-hosts cache yet, is no longer read.
1378
1415
  - Autocomplete: the bash completion of a remote command works as bash-completion completes `sudo CMD`; warnings skip hosts words the shell rewrites; history rows written by 6.26 and older are inserted with their commands in single quotes, so the local shell expands no `!`, `$` or backtick in them; the debug log (`MSSH_COMPLETE_DEBUG=1`) no longer holds the line typed in the editor.
1379
1416
  - Fix (zsh): `mssh HOSTS CMD ...<TAB>` no longer adds local file names beside the remote command's words when python-argcomplete (`#compdef -P *`) is installed.
1380
1417
  - New known limitations, each in [docs/limitations.md](docs/limitations.md):