[v0.1/SSH Review] --ssh-arg のトークン分割 UX と ControlMaster 委譲 #38

Closed
opened 2026-09-03 16:30:49 +09:00 by natsukium · 1 comment
Owner

現状

  • --ssh-arg <TOKEN>ssh に verbatim で一トークンずつ spliced される。ssh -p 2222--ssh-arg=-p --ssh-arg=2222 と二回に分けて書く必要がある(cli.md "Ad-hoc VMs and extra ssh flags")。
  • felis は ssh の文法を解釈しない("which of them take values is ssh's grammar, not felis's")。shadow parser を持たないことが principle 4 として意図的に選ばれている。
  • 結果として --ssh-arg="-p 2222" は一トークンとして ssh に渡り、ssh 側で不正引数になるが、felis は気づかない。ユーザは「なぜ繋がらないのか」を ssh の stderr から推測する必要がある。
  • window retarget / switch / sessions 系の SSH 接続は毎回 ssh child を respawn する。connect_carrierCarrier::Ssh ごとに新しい Command::new("ssh") を作る。pull_paced = false で eager-push にしているが、コネクション自体は毎回張り直し。
  • how-to/attach-over-ssh.md は「ControlMaster を自分の ssh config で有効化して」と委譲している。felis は multiplexing を管理しないと明記(explanation/architecture/ipc.md carrier choices)。

問い(1.0 でしか変えられない)

  1. --ssh-arg のトークン分割 UX をこのまま凍結するか? シェル経由の一括文字列や --ssh-option への改名を含めて再設計するか?
  2. ControlMaster の委譲をこのままドキュメントのみの対策とするか、felis 側で -o ControlMaster=auto -o ControlPath=... を自動付与するか?

破壊的変更案

  • 案A: --ssh-arg を維持しつつ、UX を改善。 --ssh-arg 一回で "-p 2222" のような空白を含む値を受けたら felis 側で分割してから splice する(shadow parser ではないが最小の分割)。同時に DialError::SshStdiossh 終了コードを構造化して返す。
  • 案B: --ssh-arg--ssh-opt にリネームし、値は ssh -o 形式のみに限定。 自由なトークン splice をやめ、-o ControlMaster=... のような key=value だけを許可。原理的にはクリーンだが ad-hoc VM の -i/-p が書けなくなる。
  • 案C: felis が ControlMaster を自動管理。 Carrier::Ssh 接続時に ControlPath=${XDG_RUNTIME_DIR}/felis/ssh-%r@%h:%p を自動付与し、初回以外は既存 master を再利用。ssh に委譲していたパフォーマンス対策を felis が持つ。~/.ssh/config の既存設定と衝突した場合はユーザ設定を優先。
  • 案D: 現行凍結。 --ssh-arg の repeat-per-token 契約と ControlMaster 委譲を reference/cli.md に normative として明記し、explanation/architecture/control-surfaces.md に「なぜ felis が ssh multiplexing を持たないか」の rationale を追記。

判定基準

  • felis --host vm --ssh-arg=-p --ssh-arg=2222 sessions listfelis ssh vm --ssh-arg=-p --ssh-arg=2222 が同一の relay_command を作ることがテストで保証されること(現行 relay_command_splices_ssh_args_before_the_destination)。
  • 初見ユーザが ~/.ssh/config を持たない VM に一発で繋げるまでの打鍵数と失敗時の診断の明確さ。
  • principle 1(embedded evaluator を持たない)と principle 4(heuristics を持たない)に反しないこと。

対象ファイル

  • crates/felis-client-core/src/connector.rs (relay_command), crates/felis-cli/src/main.rs/conn.rs, crates/felis-client/src/main.rs, crates/felis-protocol/src/messages.rs (RetargetCarrier::Ssh { ssh_args }), docs/reference/cli.md, docs/how-to/attach-over-ssh.md, docs/explanation/architecture/ipc.md

cc @natsukium

## 現状 - `--ssh-arg <TOKEN>` は `ssh` に verbatim で一トークンずつ spliced される。`ssh -p 2222` は `--ssh-arg=-p --ssh-arg=2222` と二回に分けて書く必要がある(`cli.md` "Ad-hoc VMs and extra ssh flags")。 - felis は `ssh` の文法を解釈しない("which of them take values is ssh's grammar, not felis's")。shadow parser を持たないことが principle 4 として意図的に選ばれている。 - 結果として `--ssh-arg="-p 2222"` は一トークンとして `ssh` に渡り、`ssh` 側で不正引数になるが、felis は気づかない。ユーザは「なぜ繋がらないのか」を `ssh` の stderr から推測する必要がある。 - 各 `window retarget` / `switch` / `sessions` 系の SSH 接続は毎回 `ssh` child を respawn する。`connect_carrier` は `Carrier::Ssh` ごとに新しい `Command::new("ssh")` を作る。`pull_paced = false` で eager-push にしているが、コネクション自体は毎回張り直し。 - `how-to/attach-over-ssh.md` は「ControlMaster を自分の ssh config で有効化して」と委譲している。felis は multiplexing を管理しないと明記(`explanation/architecture/ipc.md` carrier choices)。 ## 問い(1.0 でしか変えられない) 1. `--ssh-arg` のトークン分割 UX をこのまま凍結するか? シェル経由の一括文字列や `--ssh-option` への改名を含めて再設計するか? 2. ControlMaster の委譲をこのままドキュメントのみの対策とするか、felis 側で `-o ControlMaster=auto -o ControlPath=...` を自動付与するか? ## 破壊的変更案 - **案A: `--ssh-arg` を維持しつつ、UX を改善。** `--ssh-arg` 一回で `"-p 2222"` のような空白を含む値を受けたら felis 側で分割してから splice する(shadow parser ではないが最小の分割)。同時に `DialError::SshStdio` で `ssh` 終了コードを構造化して返す。 - **案B: `--ssh-arg` を `--ssh-opt` にリネームし、値は `ssh -o` 形式のみに限定。** 自由なトークン splice をやめ、`-o ControlMaster=...` のような key=value だけを許可。原理的にはクリーンだが ad-hoc VM の `-i`/`-p` が書けなくなる。 - **案C: felis が ControlMaster を自動管理。** `Carrier::Ssh` 接続時に `ControlPath=${XDG_RUNTIME_DIR}/felis/ssh-%r@%h:%p` を自動付与し、初回以外は既存 master を再利用。`ssh` に委譲していたパフォーマンス対策を felis が持つ。`~/.ssh/config` の既存設定と衝突した場合はユーザ設定を優先。 - **案D: 現行凍結。** `--ssh-arg` の repeat-per-token 契約と ControlMaster 委譲を `reference/cli.md` に normative として明記し、`explanation/architecture/control-surfaces.md` に「なぜ felis が ssh multiplexing を持たないか」の rationale を追記。 ## 判定基準 - `felis --host vm --ssh-arg=-p --ssh-arg=2222 sessions list` と `felis ssh vm --ssh-arg=-p --ssh-arg=2222` が同一の `relay_command` を作ることがテストで保証されること(現行 `relay_command_splices_ssh_args_before_the_destination`)。 - 初見ユーザが `~/.ssh/config` を持たない VM に一発で繋げるまでの打鍵数と失敗時の診断の明確さ。 - principle 1(embedded evaluator を持たない)と principle 4(heuristics を持たない)に反しないこと。 ## 対象ファイル - `crates/felis-client-core/src/connector.rs` (`relay_command`), `crates/felis-cli/src/main.rs`/`conn.rs`, `crates/felis-client/src/main.rs`, `crates/felis-protocol/src/messages.rs` (`RetargetCarrier::Ssh { ssh_args }`), `docs/reference/cli.md`, `docs/how-to/attach-over-ssh.md`, `docs/explanation/architecture/ipc.md` cc @natsukium
Author
Owner

Triaged as resolved with no code change. The current contract is deliberate and already recorded in docs/explanation/architecture/ipc.md: each --ssh-arg is one verbatim argv token, while connection multiplexing belongs to OpenSSH configuration. Splitting a shell-like string would add the parser this boundary avoids, and automatic ControlMaster management has no observed consumer that OpenSSH itself does not already serve. Reopen only with a concrete workflow that the token form plus ~/.ssh/config cannot express.

Triaged as resolved with no code change. The current contract is deliberate and already recorded in `docs/explanation/architecture/ipc.md`: each `--ssh-arg` is one verbatim argv token, while connection multiplexing belongs to OpenSSH configuration. Splitting a shell-like string would add the parser this boundary avoids, and automatic ControlMaster management has no observed consumer that OpenSSH itself does not already serve. Reopen only with a concrete workflow that the token form plus `~/.ssh/config` cannot express.
Sign in to join this conversation.
No description provided.