From ff86a421f49aa67d8abb75dccf2f30ff6316d358 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 18 Aug 2026 01:00:19 +0100 Subject: [PATCH] fix(labsim): console-apply must handle both VyOS prompts, not just $ Two failures from one strict expect, both hit while building the sim ISPs. A run that dies mid-config leaves the console parked in configuration mode. The next run then waits for the operational `$ ` prompt against a perfectly healthy VM and hangs until timeout, with nothing in the output to say why -- the box was sitting at `vyos@isp-dhcp#` the whole time. Login now accepts `# ` as well and discards the stale candidate rather than committing something nobody has seen. The same mistake at the exit step: insisting on `$ ` after `save` hung, AND left the console in config mode, which is what created the first failure for the following run. Now accepts either prompt. Known-bad, not fixed: the tool reports "committed and saved" when the set commands have not applied. Verified against the clone -- prompt showed the host-name change had landed while `grep -c dhcp-server` returned 0. The failure detection only inspects c.before for a few strings and evidently misses the real failure mode, so success is being reported without evidence. That needs fixing before this tool is trusted for anything; it is currently only safe to use with an independent check afterwards, which is how the gap was found. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yH --- labsim/console-apply.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/labsim/console-apply.py b/labsim/console-apply.py index 82b1f42..a15bb90 100755 --- a/labsim/console-apply.py +++ b/labsim/console-apply.py @@ -38,23 +38,39 @@ def main() -> int: # Log in. A freshly booted box may still be starting services, so allow a # generous window and re-prod the console rather than failing on the first # miss. + # + # `# ` matters as much as `$ `: a previous run that died mid-config leaves + # the console sitting in configuration mode, and waiting only for the + # operational prompt then hangs forever against a perfectly healthy VM. + in_config = False for _ in range(40): i = c.expect([r"login:", r"\$ ", r"# ", pexpect.TIMEOUT], timeout=15) if i == 0: c.sendline(args.user) c.expect("Password:", timeout=30) c.sendline(args.password) - c.expect(r"\$ ", timeout=60) + c.expect([r"\$ ", r"# "], timeout=60) break - if i in (1, 2): + if i == 1: + break + if i == 2: + in_config = True break c.sendline("") else: print("never reached a prompt", file=sys.stderr) return 1 - c.sendline("configure") - c.expect(r"# ", timeout=60) + if in_config: + # Drop whatever the previous run left half-built rather than committing + # a candidate nobody has seen. + print("console was left in config mode; discarding stale candidate", + file=sys.stderr) + c.sendline("discard") + c.expect(r"# ", timeout=60) + else: + c.sendline("configure") + c.expect(r"# ", timeout=60) for cmd in cmds: c.sendline(cmd) @@ -69,10 +85,14 @@ def main() -> int: commit_out = c.before or "" c.sendline("save") c.expect(r"# ", timeout=120) + # Accept either prompt on the way out. Insisting on `$ ` here hangs against + # a healthy box -- and worse, leaves the console parked in config mode, so + # the NEXT run finds a `# ` it was not expecting either. One strict expect + # turned into two failures. c.sendline("exit") - c.expect(r"\$ ", timeout=60) + c.expect([r"\$ ", r"# ", pexpect.TIMEOUT], timeout=60) c.sendline("exit") - c.close() + c.close(force=True) bad = [l for l in commit_out.splitlines() if "failed" in l.lower() or "error" in l.lower()]