From 5d3214d10cb0e89441e7c9781fe2bf17eb5053aa Mon Sep 17 00:00:00 2001 From: Simone Date: Sun, 24 May 2026 02:07:36 +0200 Subject: [PATCH] fix(template): use sys.stdin.readline() for snapshot confirm prompt input() with a prompt string on some terminals (zsh + Linux Mint) echoes ^M and the prompt doesn't flush before reading, causing the prompt to appear stuck or not accept input. Replace with explicit sys.stdout.write()+flush() + sys.stdin.readline() which guarantees the prompt is flushed before blocking on stdin, and readline() + .strip() handles both \n and \r\n line endings correctly. Tests updated to use CliRunner(input=) which populates sys.stdin inside the runner context (monkeypatching sys.stdin.readline directly is ignored by CliRunner which replaces sys.stdin entirely). Co-Authored-By: Claude Sonnet 4.6 --- src/ci_orchestrator/commands/template.py | 7 +++++-- tests/python/test_commands_template.py | 6 ++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ci_orchestrator/commands/template.py b/src/ci_orchestrator/commands/template.py index 52c568c..3bc15d4 100644 --- a/src/ci_orchestrator/commands/template.py +++ b/src/ci_orchestrator/commands/template.py @@ -60,6 +60,7 @@ import json import shutil import socket import subprocess +import sys import threading import time from datetime import UTC, datetime @@ -671,8 +672,10 @@ Register-ScheduledTask -TaskName 'CI-StaticIp' ` if recreate_snapshot: do_recreate = True else: - raw = input(f"[prepare-win] Delete and recreate '{snapshot}'? [y/N] ") - do_recreate = raw.strip().rstrip("\r").lower() in ("y", "yes") + sys.stdout.write(f"[prepare-win] Delete and recreate '{snapshot}'? [y/N] ") + sys.stdout.flush() + raw = sys.stdin.readline() + do_recreate = raw.strip().lower() in ("y", "yes") if not do_recreate: click.echo("[prepare-win] Keeping existing snapshot. Provisioning complete.") return diff --git a/tests/python/test_commands_template.py b/tests/python/test_commands_template.py index 09c1733..49888d2 100644 --- a/tests/python/test_commands_template.py +++ b/tests/python/test_commands_template.py @@ -613,8 +613,6 @@ def test_prepare_win_snapshot_exists_skip( backend.list_snapshots.return_value = ["BaseClean"] transport = _make_transport() _patch_prepare(monkeypatch, backend, transport) - monkeypatch.setattr("builtins.input", lambda _prompt="": "N") - result = CliRunner().invoke( cli, [ @@ -624,6 +622,7 @@ def test_prepare_win_snapshot_exists_skip( "--admin-password", "adminpass", "--build-password", "buildpass", ], + input="N\n", ) assert result.exit_code == 0, result.output assert "Keeping existing snapshot" in result.output @@ -644,8 +643,6 @@ def test_prepare_win_snapshot_exists_recreate( monkeypatch.setattr(tmpl_mod, "_wait_tcp", lambda *_: True) monkeypatch.setattr(tmpl_mod, "_wait_winrm", lambda *_: None) monkeypatch.setattr(tmpl_mod.time, "sleep", lambda _: None) - monkeypatch.setattr("builtins.input", lambda _prompt="": "Y") - def _sp_run(cmd: list[str], **_: object) -> None: sp_calls.append(cmd) @@ -660,6 +657,7 @@ def test_prepare_win_snapshot_exists_recreate( "--admin-password", "adminpass", "--build-password", "buildpass", ], + input="Y\n", ) assert result.exit_code == 0, result.output assert any("deleteSnapshot" in " ".join(c) for c in sp_calls)