Remove CLI backend assumptions and add docker fallback prompt #434

Merged
didericis merged 5 commits from remove-cli-backend-assumptions into main 2026-07-20 18:32:49 -04:00
Collaborator

Closes #344.

Summary

  • Remove --backend flag from cli.py start: backend selection is now driven exclusively by BOT_BOTTLE_BACKEND env var or two-tier auto-selection. resume and ./cli.py backend still accept explicit names.
  • Fix cross-platform crash in cli.py cleanup: skip unavailable backends (e.g. macos-container on Linux) rather than calling prepare_cleanup() which called require_container() and died.
  • Remove hardcoded --backend=macos-container from firecracker/util.py error message.
  • Add two-tier auto-selection with docker fallback prompt: when no VM backend is available, warn that docker is less secure and offer an interactive [i]nstall instructions / [d]ocker anyway / [q]uit prompt. When docker is also absent, print VM install instructions and exit.

The VM preference is platform-aware: macos-container on macOS, firecracker on KVM-capable Linux. A KVM-capable host without the firecracker binary still selects firecracker (unchanged) so its preflight prints an install pointer instead of silently falling back to docker.

Closes #344. ## Summary - **Remove `--backend` flag from `cli.py start`**: backend selection is now driven exclusively by `BOT_BOTTLE_BACKEND` env var or two-tier auto-selection. `resume` and `./cli.py backend` still accept explicit names. - **Fix cross-platform crash in `cli.py cleanup`**: skip unavailable backends (e.g. `macos-container` on Linux) rather than calling `prepare_cleanup()` which called `require_container()` and died. - **Remove hardcoded `--backend=macos-container`** from `firecracker/util.py` error message. - **Add two-tier auto-selection with docker fallback prompt**: when no VM backend is available, warn that docker is less secure and offer an interactive `[i]nstall instructions / [d]ocker anyway / [q]uit` prompt. When docker is also absent, print VM install instructions and exit. The VM preference is platform-aware: `macos-container` on macOS, `firecracker` on KVM-capable Linux. A KVM-capable host without the `firecracker` binary still selects `firecracker` (unchanged) so its preflight prints an install pointer instead of silently falling back to docker.
didericis-claude added 1 commit 2026-07-20 15:13:36 -04:00
fix: remove CLI backend assumptions and add docker fallback prompt
test / stage-firecracker-inputs (pull_request) Successful in 3s
tracker-policy-pr / check-pr (pull_request) Successful in 12s
lint / lint (push) Successful in 42s
test / integration-docker (pull_request) Successful in 33s
test / unit (pull_request) Successful in 37s
test / build-infra (pull_request) Successful in 3m47s
test / integration-firecracker (pull_request) Successful in 1m39s
test / coverage (pull_request) Failing after 1m27s
test / publish-infra (pull_request) Has been skipped
8b5b5730ae
- Remove hardcoded --backend=macos-container flag reference in
  firecracker/util.py require_firecracker() error message
- Remove --backend flag from cli.py start; backend selection now
  driven exclusively by BOT_BOTTLE_BACKEND env var or auto-selection
- Skip unavailable backends in cli.py cleanup (fixes crash on Linux
  when macos-container.prepare_cleanup calls require_container())
- Add two-tier auto-selection: VM backend first (macos-container on
  macOS, firecracker on Linux+KVM); fall back to docker with a
  security warning and interactive i/d/q prompt; exit if docker
  also unavailable and print VM install instructions

Closes #344
didericis added 1 commit 2026-07-20 15:26:07 -04:00
test: cover _read_tty_line, macos install branch, and reply==i path
test / stage-firecracker-inputs (pull_request) Successful in 3s
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / integration-docker (pull_request) Successful in 14s
lint / lint (push) Successful in 51s
test / unit (pull_request) Successful in 1m29s
test / build-infra (pull_request) Successful in 3m30s
test / integration-firecracker (pull_request) Successful in 1m34s
test / coverage (pull_request) Successful in 1m30s
test / publish-infra (pull_request) Has been skipped
b1ebc6f1b8
didericis reviewed 2026-07-20 15:40:57 -04:00
@@ -669,3 +669,3 @@
def _default_backend_name() -> str:
def _read_tty_line() -> str:
Owner

This logic is undoubtably used other places, should be called from a util script/consolidate where this logic is defined

This logic is undoubtably used other places, should be called from a util script/consolidate where this logic is defined
didericis marked this conversation as resolved
didericis-claude reviewed 2026-07-20 15:42:31 -04:00
@@ -669,3 +669,3 @@
def _default_backend_name() -> str:
def _read_tty_line() -> str:
Author
Collaborator

Good call — the same logic lives in cli/_common.py::read_tty_line. Will move it to bot_bottle/util.py so both the CLI layer and the backend can import from one place.

Good call — the same logic lives in `cli/_common.py::read_tty_line`. Will move it to `bot_bottle/util.py` so both the CLI layer and the backend can import from one place.
didericis added 1 commit 2026-07-20 15:44:40 -04:00
refactor: consolidate read_tty_line into bot_bottle/util.py
tracker-policy-pr / check-pr (pull_request) Successful in 10s
test / integration-docker (pull_request) Successful in 26s
test / unit (pull_request) Successful in 36s
lint / lint (push) Failing after 42s
test / stage-firecracker-inputs (pull_request) Successful in 2s
test / build-infra (pull_request) Successful in 3m57s
test / integration-firecracker (pull_request) Successful in 1m55s
test / coverage (pull_request) Successful in 2m14s
test / publish-infra (pull_request) Has been skipped
b4b73a8acc
Remove the private _read_tty_line duplicate from backend/__init__.py and
the local definition from cli/_common.py. Both now import from the
shared bot_bottle.util module.
didericis-codex requested changes 2026-07-20 15:47:54 -04:00
didericis-codex left a comment
Collaborator

Found one blocking issue:

  • P1 — start --headless can now block on an interactive backend prompt. The headless launch eventually calls get_bottle_backend() without conveying that it is non-interactive. On a Docker-only host with no BOT_BOTTLE_BACKEND, _auto_select_backend() reads /dev/tty for the [i/d/q] choice. This violates the documented headless contract and can hang CI, webhook dispatch, and orchestrators. Headless selection should either fail immediately with an actionable message or select Docker according to an explicit non-interactive policy; it must not prompt.

The focused suite passed (67 tests via unittest), but the headless tests mock _launch_bottle before backend resolution, so they do not exercise this path.

Found one blocking issue: - **P1 — `start --headless` can now block on an interactive backend prompt.** The headless launch eventually calls `get_bottle_backend()` without conveying that it is non-interactive. On a Docker-only host with no `BOT_BOTTLE_BACKEND`, `_auto_select_backend()` reads `/dev/tty` for the `[i/d/q]` choice. This violates the documented headless contract and can hang CI, webhook dispatch, and orchestrators. Headless selection should either fail immediately with an actionable message or select Docker according to an explicit non-interactive policy; it must not prompt. The focused suite passed (67 tests via unittest), but the headless tests mock `_launch_bottle` before backend resolution, so they do not exercise this path.
didericis added 1 commit 2026-07-20 15:58:23 -04:00
fix(backend): don't prompt in headless/non-interactive backend selection
lint / lint (push) Failing after 45s
test / unit (pull_request) Successful in 1m34s
test / integration-docker (pull_request) Successful in 12s
tracker-policy-pr / check-pr (pull_request) Successful in 20s
test / stage-firecracker-inputs (pull_request) Successful in 3s
test / build-infra (pull_request) Successful in 3m21s
test / integration-firecracker (pull_request) Successful in 1m30s
test / coverage (pull_request) Successful in 1m30s
test / publish-infra (pull_request) Has been skipped
d9e685e860
_auto_select_backend gains a prompt parameter (default True). When
prompt=False the docker-fallback [i/d/q] menu is skipped and the call
dies immediately with an actionable message ("set
BOT_BOTTLE_BACKEND=docker or install a VM backend"), preventing hangs
in CI, webhook dispatch, and orchestrator launches.

prepare_with_preflight passes prompt=not spec.headless so the headless
start path can never block waiting for TTY input it cannot receive.
didericis-claude reviewed 2026-07-20 15:59:13 -04:00
didericis-claude left a comment
Author
Collaborator

Fixed in d9e685e.

_auto_select_backend now accepts prompt: bool = True. When prompt=False the docker-fallback [i/d/q] menu is replaced by an immediate die() with an actionable message ("set BOT_BOTTLE_BACKEND=docker or install the VM backend"), so no TTY read can block a headless caller.

prepare_with_preflight passes prompt=not spec.headless, wiring the flag from BottleSpec into backend selection without threading a new parameter through _launch_bottle. Added test_docker_fallback_non_interactive_dies to cover the new branch.

Fixed in d9e685e. `_auto_select_backend` now accepts `prompt: bool = True`. When `prompt=False` the docker-fallback `[i/d/q]` menu is replaced by an immediate `die()` with an actionable message (`"set BOT_BOTTLE_BACKEND=docker or install the VM backend"`), so no TTY read can block a headless caller. `prepare_with_preflight` passes `prompt=not spec.headless`, wiring the flag from `BottleSpec` into backend selection without threading a new parameter through `_launch_bottle`. Added `test_docker_fallback_non_interactive_dies` to cover the new branch.
@@ -682,0 +726,4 @@
"docker is less secure than VM backends — "
"containers share the host kernel."
)
if not prompt:
Author
Collaborator

Added if not prompt: die(...) here. When prompt=False (headless / CI) the docker-fallback prompt is bypassed entirely — die() emits an actionable message and exits instead of blocking on read_tty_line().

Added `if not prompt: die(...)` here. When `prompt=False` (headless / CI) the docker-fallback prompt is bypassed entirely — `die()` emits an actionable message and exits instead of blocking on `read_tty_line()`.
didericis added 1 commit 2026-07-20 16:11:13 -04:00
fix(lint): mark read_tty_line as an explicit re-export in _common.py
test / coverage (push) Successful in 2m6s
test / publish-infra (push) Successful in 2m38s
test / build-infra (push) Successful in 3m49s
test / integration-firecracker (push) Successful in 2m14s
test / stage-firecracker-inputs (pull_request) Successful in 2s
tracker-policy-pr / check-pr (pull_request) Successful in 12s
test / integration-docker (pull_request) Successful in 27s
test / unit (pull_request) Successful in 34s
test / build-infra (pull_request) Successful in 3m39s
test / integration-firecracker (pull_request) Successful in 1m33s
test / coverage (pull_request) Successful in 2m2s
test / publish-infra (pull_request) Has been skipped
test / stage-firecracker-inputs (push) Successful in 3s
test / unit (push) Successful in 34s
test / integration-docker (push) Successful in 32s
lint / lint (push) Successful in 43s
Update Quality Badges / update-badges (push) Failing after 39s
c7375051fd
Pyright reportUnusedImport fired because the bare import is not used
within _common.py itself. The X as X idiom signals an intentional
public re-export without requiring __all__.
didericis-codex approved these changes 2026-07-20 18:30:13 -04:00
didericis-codex left a comment
Collaborator

Re-reviewed at c7375051fd3b0afb5a9e825c7dc6cd7d6063cacc. The prior blocking issue is resolved: headless launches now pass prompt=False into backend auto-selection, which exits with actionable guidance instead of reading from a TTY, and the selector has direct regression coverage. No new findings. The 68 focused affected tests pass.

Re-reviewed at `c7375051fd3b0afb5a9e825c7dc6cd7d6063cacc`. The prior blocking issue is resolved: headless launches now pass `prompt=False` into backend auto-selection, which exits with actionable guidance instead of reading from a TTY, and the selector has direct regression coverage. No new findings. The 68 focused affected tests pass.
didericis-codex approved these changes 2026-07-20 18:30:21 -04:00
didericis-codex left a comment
Collaborator

Re-reviewed at c7375051fd3b0afb5a9e825c7dc6cd7d6063cacc. The prior blocking issue is resolved and I found no new issues. The 68 focused affected tests pass.

Re-reviewed at `c7375051fd3b0afb5a9e825c7dc6cd7d6063cacc`. The prior blocking issue is resolved and I found no new issues. The 68 focused affected tests pass.
didericis merged commit c7375051fd into main 2026-07-20 18:32:49 -04:00
didericis deleted branch remove-cli-backend-assumptions 2026-07-20 18:32:49 -04:00
Sign in to join this conversation.