[v0.1/CLI Review] frontend への --config 転送 / 拒否の欠落は 1.0 で凍結すべき #136

Closed
opened 2026-09-05 11:37:30 +09:00 by natsukium · 2 comments
Owner

背景

felis --config <PATH> frontend <name> ... が黙って無視される。crates/felis-cli/src/main.rsCmd::Frontend 腕は global_passthrough を呼ばず、global_carrier のみ拒否する。結果:

  • felis --config /tmp/work.toml frontend tuifelis-tui はデフォルトパスを読む。ユーザは「work 用 config で tui を開いた」と信じるが実際は反映されない。
  • felis --host devbox frontend ... は「拒否」されるが、--config は拒否も転送もされない中途半端。

attach / bare felisglobal_passthrough--config/--host/--socket/--ssh-arg を正規に転送する。frontend だけが例外で、しかも cli_sessions::reject_global_carrier の拒否対象外になっている。

これは reference/cli.md「Global options」「Alternate frontends」で「--config は window launch / config / doctor のみが読む」「frontend は自前の flag を自分で dial する」とある記述とも不整合: felis frontend は window launch ではないが、ユーザ視点では felis -- --cmd と同じ「window を開く」操作として --config を期待する。

問い

1.0 で凍結する前にどちらで統一するか:

  • 案A: frontend でも --config を転送するattach / bare と同様に global_passthrough に含め、frontend 側で再パースさせる。--host/--socket/--ssh-arg は「frontend が自前で持つならそちら、なければ拒否」の現行を維持するが、--config だけは felis 側で解決してから渡す(既に resolve_config_source で絶対パス化している)。
  • 案B: frontend--config を拒否するattach と同じく window launch として扱わず、frontend <name> は完全に外部プログラムとして「felis は何も転送しない」と明記し、--configfelis-<name> --config の形で書かせる。

案Aがユーザの期待に近い(felis --config X -- htop と同じ書き方で felis --config X frontend tui も動く)。案Bは「felis は薄いラッパ」という原則に忠実だが、既存の bare/attach が転送する以上、非対称が残る。

いずれにせよ 1.0 で「どの global flag がどの Cmd で転送/拒否/無視か」を表で凍結する必要がある。

提案

  • 案Aを採用し、crates/felis-cli/src/main.rsCmd::Frontend 腕で global_passthrough と同様に --config を受け取り、exec_frontend--config <abs> として渡す。--host/--socket/--ssh-arg は現行どおり拒否のまま。
  • あるいは案Bなら Cmd::Frontend の入口で config.is_some()reject_global_carrier と同等の framing で拒否し、reference/cli.md「Alternate frontends」に「--configfelis frontend <name> --config で指定」と追記。
  • 判定を reference/cli.md「Global options」表と reference/control-surfaces.md「Placement」に「転送/拒否/無視マトリクス」として固定し、cli_sessions::reject_global_carrierverb_reading_no_config の分岐テストに golden 化。

判定基準

  • felis --config <PATH> --help / felis --config <PATH> frontend --help / felis attach --help の help が、どの位置で --config が効くかを推測可能にすること。
  • felis --config /nonexistent frontend tui が 1.0 の契約どおり exit 2 で失敗するか、または明示的に拒否されるか、どちらかに一意に決まること(黙ってデフォルトを読むのは不可)。
  • just test の CLI パーステストが frontend の global flag 組み合わせを網羅していること。

対象ファイル

  • crates/felis-cli/src/main.rs (global_passthrough, verb_reading_no_config, Cmd::Frontend 腕)
  • crates/felis-client/src/main.rs / crates/felis-client-core/src/config.rs (ConfigSource 解決)
  • docs/reference/cli.md「Global options」「Alternate frontends」
  • docs/reference/control-surfaces.md

Parent: #12 および #11 / #3

## 背景 `felis --config <PATH> frontend <name> ...` が黙って無視される。`crates/felis-cli/src/main.rs` の `Cmd::Frontend` 腕は `global_passthrough` を呼ばず、`global_carrier` のみ拒否する。結果: - `felis --config /tmp/work.toml frontend tui` → `felis-tui` はデフォルトパスを読む。ユーザは「work 用 config で tui を開いた」と信じるが実際は反映されない。 - `felis --host devbox frontend ...` は「拒否」されるが、`--config` は拒否も転送もされない中途半端。 `attach` / bare `felis` は `global_passthrough` で `--config`/`--host`/`--socket`/`--ssh-arg` を正規に転送する。`frontend` だけが例外で、しかも `cli_sessions::reject_global_carrier` の拒否対象外になっている。 これは `reference/cli.md`「Global options」「Alternate frontends」で「`--config` は window launch / `config` / `doctor` のみが読む」「frontend は自前の flag を自分で dial する」とある記述とも不整合: `felis frontend` は window launch ではないが、ユーザ視点では `felis -- --cmd` と同じ「window を開く」操作として `--config` を期待する。 ## 問い 1.0 で凍結する前にどちらで統一するか: - **案A: `frontend` でも `--config` を転送する** — `attach` / bare と同様に `global_passthrough` に含め、`frontend` 側で再パースさせる。`--host/--socket/--ssh-arg` は「frontend が自前で持つならそちら、なければ拒否」の現行を維持するが、`--config` だけは felis 側で解決してから渡す(既に `resolve_config_source` で絶対パス化している)。 - **案B: `frontend` で `--config` を拒否する** — `attach` と同じく window launch として扱わず、`frontend <name>` は完全に外部プログラムとして「`felis` は何も転送しない」と明記し、`--config` は `felis-<name> --config` の形で書かせる。 案Aがユーザの期待に近い(`felis --config X -- htop` と同じ書き方で `felis --config X frontend tui` も動く)。案Bは「`felis` は薄いラッパ」という原則に忠実だが、既存の bare/`attach` が転送する以上、非対称が残る。 いずれにせよ 1.0 で「どの global flag がどの Cmd で転送/拒否/無視か」を表で凍結する必要がある。 ## 提案 - 案Aを採用し、`crates/felis-cli/src/main.rs` の `Cmd::Frontend` 腕で `global_passthrough` と同様に `--config` を受け取り、`exec_frontend` に `--config <abs>` として渡す。`--host/--socket/--ssh-arg` は現行どおり拒否のまま。 - あるいは案Bなら `Cmd::Frontend` の入口で `config.is_some()` を `reject_global_carrier` と同等の framing で拒否し、`reference/cli.md`「Alternate frontends」に「`--config` は `felis frontend <name> --config` で指定」と追記。 - 判定を `reference/cli.md`「Global options」表と `reference/control-surfaces.md`「Placement」に「転送/拒否/無視マトリクス」として固定し、`cli_sessions::reject_global_carrier` と `verb_reading_no_config` の分岐テストに golden 化。 ## 判定基準 - `felis --config <PATH> --help` / `felis --config <PATH> frontend --help` / `felis attach --help` の help が、どの位置で `--config` が効くかを推測可能にすること。 - `felis --config /nonexistent frontend tui` が 1.0 の契約どおり exit 2 で失敗するか、または明示的に拒否されるか、どちらかに一意に決まること(黙ってデフォルトを読むのは不可)。 - `just test` の CLI パーステストが `frontend` の global flag 組み合わせを網羅していること。 ## 対象ファイル - `crates/felis-cli/src/main.rs` (`global_passthrough`, `verb_reading_no_config`, `Cmd::Frontend` 腕) - `crates/felis-client/src/main.rs` / `crates/felis-client-core/src/config.rs` (`ConfigSource` 解決) - `docs/reference/cli.md`「Global options」「Alternate frontends」 - `docs/reference/control-surfaces.md` Parent: #12 および #11 / #3
Author
Owner

Triage (2026-09-05)

Verdict: accept, P1. Recommend option B (reject). Verified in crates/felis-cli/src/main.rs Cmd::Frontend: --config is neither forwarded nor refused. The carrier refusal already records the reason forwarding is wrong (felis does not know felis-<name>'s flags, so it would guess a spelling), and --config has the same problem: a third-party frontend need not accept that flag. Option A would trade a silent ignore for a possible exec failure with a foreign usage error. Reject with the same framing as the carrier refusal, then freeze the forward/refuse/ignore matrix for every global flag × Cmd in reference/cli.md and reference/control-surfaces.md, with a parser test per cell. Parent: #55.

## Triage (2026-09-05) **Verdict: accept, P1. Recommend option B (reject).** Verified in `crates/felis-cli/src/main.rs` `Cmd::Frontend`: `--config` is neither forwarded nor refused. The carrier refusal already records the reason forwarding is wrong (`felis` does not know `felis-<name>`'s flags, so it would guess a spelling), and `--config` has the same problem: a third-party frontend need not accept that flag. Option A would trade a silent ignore for a possible exec failure with a foreign usage error. Reject with the same framing as the carrier refusal, then freeze the forward/refuse/ignore matrix for every global flag × `Cmd` in `reference/cli.md` and `reference/control-surfaces.md`, with a parser test per cell. Parent: #55.
Author
Owner

Triage plan (2026-09-05)

Source-grounded triage against main at 07a77ae4, reviewed through six rounds of an independent reviewer (pi luna then sol); every finding was verified against the source and folded in, and the design decisions below were settled with the maintainer on 2026-09-05. The order that supersedes the tracker's is posted on #12: this issue lands before #29 (schemas, goldens, and reference tables freeze there).

felis --config … frontend <name>: refuse (already true), freeze the matrix

Claim check

The premise is stale. Since f2083767 (2026-09-04, the --config launch
option from #27), verb_reading_no_config in crates/felis-cli/src/main.rs:661-697
lists Cmd::Frontend, and main.rs:412-423 exits 2 (usage) with
"--config selects the config.toml a window launch, felis config, or
felis doctor reads; this verb reads none" before any resolution. So
felis --config /tmp/work.toml frontend tui is refused, not ignored:
option B is already in effect, with the same reasoning as the carrier
refusal (main.rs:535-537: felis does not know felis-<name>'s flag
spelling). The verdict comment ("neither forwarded nor refused") was
verified against a tree before f2083767.

What is still missing:

  • docs/reference/cli.md:292-298 "Alternate frontends" names only the
    carrier flags as refused; --config is covered only by the generic
    sentence at :16-20. No table states, per verb, which global flag is
    forwarded, refused, or read in-process.
  • crates/felis-cli/src/tests.rs:620-640 pins the refusal per verb name
    but not the whole matrix (the forwarded set for bare/attach lives in
    global_passthrough, main.rs:702-720, tested separately).

Verdict

accept, narrowed to docs + one matrix test, plus three small refusals.
The frontend case itself needs no code; the matrix sweep turns up three
forms that still drop a global silently (--version, completions,
__mangen), and those become refusals in the same PR.

Approach

  • docs/reference/cli.md "Global options": add a matrix, rows = the four
    globals (--config, --host, --socket, --ssh-arg), columns = verb
    groups: window launch (bare, attach) → forwarded; frontend → refused
    (both --config and carrier); config--config read in-process,
    carrier refused; doctor its own column → --config read in-process,
    carrier used (main.rs:453-461: --host makes the daemon row
    report the remote daemon); window retarget → all four refused
    (main.rs:412-423,581-590); headless verbs →
    --config refused, carrier used; __complete-sessions gets its own
    cells: --config refused, --socket used, --host/--ssh-arg
    no candidates, exit 0 (cli_completions.rs:10-24, the recorded
    no-SSH-on-TAB decision). Three rows are behavior changes, all of the
    same kind (a flag silently dropped): felis --version
    (main.rs:371-387) runs before any validation and ignores all four
    globals; completions and __mangen ignore the carrier flags. Maintainer
    decision (2026-09-05): all three refuse — --version with the ArgumentConflict message it
    already uses for a trailing verb, the two verbs through
    reject_global_carrier.
    "Alternate frontends": add the --config sentence ("write it after the
    name in the frontend's own spelling").
  • docs/reference/control-surfaces.md "Placement": one line pointing at
    the matrix (no duplicate table).
  • Test: one table-driven test in crates/felis-cli/src/tests.rs that
    walks every Cmd variant plus the two non-Cmd forms (bare launch,
    cmd = None; top-level --version) × global flag and asserts
    forwarded / refused / used / in-process, so a new verb without a row
    fails to compile or fails the test.

Dependencies / risk / labels

None. S. Keep priority/P1, release/v0.1.0 (the reference table
freezes). CHANGELOG.md for the three new refusals. Parent #55.

## Triage plan (2026-09-05) Source-grounded triage against `main` at `07a77ae4`, reviewed through six rounds of an independent reviewer (`pi` luna then sol); every finding was verified against the source and folded in, and the design decisions below were settled with the maintainer on 2026-09-05. The order that supersedes the tracker's is posted on #12: this issue lands before #29 (schemas, goldens, and reference tables freeze there). **`felis --config … frontend <name>`: refuse (already true), freeze the matrix** ### Claim check The premise is stale. Since `f2083767` (2026-09-04, the `--config` launch option from #27), `verb_reading_no_config` in `crates/felis-cli/src/main.rs:661-697` lists `Cmd::Frontend`, and `main.rs:412-423` exits `2` (`usage`) with "--config selects the config.toml a window launch, `felis config`, or `felis doctor` reads; this verb reads none" before any resolution. So `felis --config /tmp/work.toml frontend tui` is refused, not ignored: option B is already in effect, with the same reasoning as the carrier refusal (`main.rs:535-537`: felis does not know `felis-<name>`'s flag spelling). The verdict comment ("neither forwarded nor refused") was verified against a tree before `f2083767`. What is still missing: - `docs/reference/cli.md:292-298` "Alternate frontends" names only the carrier flags as refused; `--config` is covered only by the generic sentence at `:16-20`. No table states, per verb, which global flag is forwarded, refused, or read in-process. - `crates/felis-cli/src/tests.rs:620-640` pins the refusal per verb name but not the whole matrix (the forwarded set for bare/`attach` lives in `global_passthrough`, `main.rs:702-720`, tested separately). ### Verdict **accept, narrowed to docs + one matrix test, plus three small refusals.** The frontend case itself needs no code; the matrix sweep turns up three forms that still drop a global silently (`--version`, `completions`, `__mangen`), and those become refusals in the same PR. ### Approach - `docs/reference/cli.md` "Global options": add a matrix, rows = the four globals (`--config`, `--host`, `--socket`, `--ssh-arg`), columns = verb groups: window launch (bare, `attach`) → forwarded; `frontend` → refused (both `--config` and carrier); `config` → `--config` read in-process, carrier refused; `doctor` its own column → `--config` read in-process, carrier *used* (`main.rs:453-461`: `--host` makes the daemon row report the remote daemon); `window retarget` → all four refused (`main.rs:412-423,581-590`); headless verbs → `--config` refused, carrier used; `__complete-sessions` gets its own cells: `--config` refused, `--socket` used, `--host`/`--ssh-arg` → no candidates, exit 0 (`cli_completions.rs:10-24`, the recorded no-SSH-on-TAB decision). Three rows are behavior changes, all of the same kind (a flag silently dropped): `felis --version` (`main.rs:371-387`) runs before any validation and ignores all four globals; `completions` and `__mangen` ignore the carrier flags. Maintainer decision (2026-09-05): all three refuse — `--version` with the `ArgumentConflict` message it already uses for a trailing verb, the two verbs through `reject_global_carrier`. "Alternate frontends": add the `--config` sentence ("write it after the name in the frontend's own spelling"). - `docs/reference/control-surfaces.md` "Placement": one line pointing at the matrix (no duplicate table). - Test: one table-driven test in `crates/felis-cli/src/tests.rs` that walks every `Cmd` variant plus the two non-`Cmd` forms (bare launch, `cmd = None`; top-level `--version`) × global flag and asserts forwarded / refused / used / in-process, so a new verb without a row fails to compile or fails the test. ### Dependencies / risk / labels None. **S.** Keep `priority/P1`, `release/v0.1.0` (the reference table freezes). `CHANGELOG.md` for the three new refusals. Parent #55.
Sign in to join this conversation.
No description provided.