You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Status 2026-07-31 — the symptom is fixed; the mechanism is not explained. B9 of #110 bisected this cleanly to #111 (the console-reader sentinel framing). What remains, and all this issue now tracks, is why a host-side console-reader defect wedged a guest service. Demoted to P3 research: nothing is failing, but an unexplained fix is a fix that can come back.
What was happening
start-stop.test.ts → clean() resets disk to factory defaults reached a healthy post-reset RouterOS 7.21.5 guest on linux-arm64, but REST never became ready during the 480 s boot envelope.
The #101/#105/#107 forensics localized it precisely:
QEMU stays alive and the serial console answers quickly;
DHCP has 10.0.2.15, uptime reaches 8+ minutes, no firewall filter rules;
ssh, API, API-SSL and WinBox are served;
HTTPS/443 cleanly refuses, proving the guest TCP stack can answer;
only HTTP/80 black-holes;
conntrack proves the forwarded SYNs reach RouterOS and receive no reply;
all 96 REST probes time out and zero HTTP requests complete.
A guest-side, service-specific www failure after clean() — not a generic boot, DHCP, slirp, hostfwd, or firewall failure.
Holding RouterOS 7.21.5 (pinned by version, not channel), arm64, TCG and the test constant, and varying only the system emulator, QEMU 8.2.2 → 11.0.2 moved the failing test by ~1 second (81.9/83.4/75.1 s vs 82.5 s). The QEMU/virtio hypothesis below is dead: the "every observed failure ran 8.2.2" correlation was confounded by code state, because every observed failure also ran pre-#111 code.
The baseline did not reproduce on current code, so B9 bisected code state instead:
Old code 4/4 fail, new code 7/7 pass, on current runner images. Both 9ce1932 failures reproduce this issue byte-identically — 96 probes [probe-timeout=96], BOOT_TIMEOUT at 478 s. So it was deterministic all along, and #111 fixed it. #112 was right to disclaim it: 2808143 has #111 but not #112 and already passes.
Why does correcting a host-side console reader fix a guest-side www black hole?
Until that is answered, #111 is a fix by coincidence as far as this issue is concerned, and any future change to console reply framing could reintroduce this without anyone connecting the two.
Hypothesis A — the clean()/provisioning sequence silently mis-executed
Pre-#111, waitForFinalPrompt could end a read on a prompt redraw and hand back a truncated or empty payload for a large reply. The test provisions over the serial console before the clean(). If a command in that sequence was believed to have run — or its reply was parsed into a wrong branch — the guest could be left with www disabled, bound elsewhere, or otherwise not serving, while every other service is untouched.
This fits the evidence best. It is the only hypothesis on the table that explains why exactly one service failed while ssh/API/WinBox stayed up, HTTPS refused cleanly, and the guest was otherwise healthy for 8+ minutes.
Hypothesis B — serial chardev backpressure
Raised in the B9 comment: consoleExec sessions persist across calls (console.ts:290) and the serial chardev is a server socket, so a client that connects and stops draining blocks QEMU's chardev write and can stall the guest. That would explain a SYN-accepted-but-never-answered signature and the empty console reply forensic.
It does not fit the selective failure. A stalled guest does not keep serving ssh, API, API-SSL and WinBox, and does not cleanly RST on 443. Backpressure is a real hazard worth knowing about — it is reachable from any future code that attaches to serial and stops reading — but as an explanation for this wedge it is contradicted by the host-side port probes, which do not depend on the console at all.
The experiment that discriminates them
Cheap, and the repro is known-good: reproduce the wedge on 9ce1932 (4/4 today), then query the wedged guest over ssh, not serial — ssh is unaffected by the console-reader defect and was demonstrably being served throughout.
# on a wedged pre-#111 guest
/ip/service/print detail # is www disabled / bound to a different address?
/log/print where topics~"web"
/system/resource/print
www disabled, misbound, or absent → Hypothesis A, and the follow-up is which command in the provisioning/clean() sequence mis-executed under a truncated reply.
www enabled, listening, on the right address, and still not answering → A is out; look harder at B or at a RouterOS www state bug after factory reset, and note that the console reader would then be affecting the guest through a path nobody has identified.
Everything else stays constant: same ref, same 7.21.5, arm64, TCG, same test.
Corrections to earlier hypotheses (kept so they are not re-derived)
QEMU version was never the variable — measured, above.
The local www-abort-damage lab does not explain this. It proved abort→completed-request alternation can cause resets, but aborts without completions were harmless — and this report has 96 aborts and zero completions. That lab remains relevant to REST: first post-credential-change request intermittently resets on linux-arm64 #69's reset family, not here.
The mechanism connecting the console-reader defect to the www black hole is identified, or the discriminating experiment above returns a documented negative and this issue closes as "symptom fixed, mechanism not reproducible on current code".
The durable RouterOS/QEMU fact — including a negative — is recorded in the governing lab/design/instruction doc, not only in this issue.
If Hypothesis B survives at all, serial-chardev backpressure is written down where the next person attaching to serial will see it (qemu.instructions.md), independent of whether it caused this.
No regression test is owed here: #111 shipped the anchor test that keeps the reader honest, and B9's bisect is the regression evidence for this symptom.
Tracked in Wave 2 of #110 (B9, done). Correlated with #69 but not merged — see #69's own mechanism boundary. Historical evidence remains in the comments.
What was happening
start-stop.test.ts→clean() resets disk to factory defaultsreached a healthy post-reset RouterOS 7.21.5 guest onlinux-arm64, but REST never became ready during the 480 s boot envelope.The #101/#105/#107 forensics localized it precisely:
10.0.2.15, uptime reaches 8+ minutes, no firewall filter rules;A guest-side, service-specific
wwwfailure afterclean()— not a generic boot, DHCP, slirp, hostfwd, or firewall failure.The bisect (B9 of #110)
Holding RouterOS 7.21.5 (pinned by version, not channel), arm64, TCG and the test constant, and varying only the system emulator, QEMU 8.2.2 → 11.0.2 moved the failing test by ~1 second (81.9/83.4/75.1 s vs 82.5 s). The QEMU/virtio hypothesis below is dead: the "every observed failure ran 8.2.2" correlation was confounded by code state, because every observed failure also ran pre-#111 code.
The baseline did not reproduce on current code, so B9 bisected code state instead:
clean()test9ce19322808143HEADOld code 4/4 fail, new code 7/7 pass, on current runner images. Both
9ce1932failures reproduce this issue byte-identically —96 probes [probe-timeout=96],BOOT_TIMEOUTat 478 s. So it was deterministic all along, and #111 fixed it. #112 was right to disclaim it:2808143has #111 but not #112 and already passes.Full result: #79 (comment)
The open question
Why does correcting a host-side console reader fix a guest-side
wwwblack hole?Until that is answered, #111 is a fix by coincidence as far as this issue is concerned, and any future change to console reply framing could reintroduce this without anyone connecting the two.
Hypothesis A — the
clean()/provisioning sequence silently mis-executedPre-#111,
waitForFinalPromptcould end a read on a prompt redraw and hand back a truncated or empty payload for a large reply. The test provisions over the serial console before theclean(). If a command in that sequence was believed to have run — or its reply was parsed into a wrong branch — the guest could be left withwwwdisabled, bound elsewhere, or otherwise not serving, while every other service is untouched.This fits the evidence best. It is the only hypothesis on the table that explains why exactly one service failed while ssh/API/WinBox stayed up, HTTPS refused cleanly, and the guest was otherwise healthy for 8+ minutes.
Hypothesis B — serial chardev backpressure
Raised in the B9 comment:
consoleExecsessions persist across calls (console.ts:290) and the serial chardev is a server socket, so a client that connects and stops draining blocks QEMU's chardev write and can stall the guest. That would explain a SYN-accepted-but-never-answered signature and theempty console replyforensic.It does not fit the selective failure. A stalled guest does not keep serving ssh, API, API-SSL and WinBox, and does not cleanly
RSTon 443. Backpressure is a real hazard worth knowing about — it is reachable from any future code that attaches to serial and stops reading — but as an explanation for this wedge it is contradicted by the host-side port probes, which do not depend on the console at all.The experiment that discriminates them
Cheap, and the repro is known-good: reproduce the wedge on
9ce1932(4/4 today), then query the wedged guest over ssh, not serial — ssh is unaffected by the console-reader defect and was demonstrably being served throughout.wwwdisabled, misbound, or absent → Hypothesis A, and the follow-up is which command in the provisioning/clean()sequence mis-executed under a truncated reply.wwwenabled, listening, on the right address, and still not answering → A is out; look harder at B or at a RouterOSwwwstate bug after factory reset, and note that the console reader would then be affecting the guest through a path nobody has identified.Everything else stays constant: same ref, same 7.21.5, arm64, TCG, same test.
Corrections to earlier hypotheses (kept so they are not re-derived)
clean()leavingstate.usernaming the erased user was a real defect (fixed in fix(clean): drop the credential state factory reset erased (#79) #112) but not the cause; a deleted user returns401, which is enough for readiness.www-abort-damagelab does not explain this. It proved abort→completed-request alternation can cause resets, but aborts without completions were harmless — and this report has 96 aborts and zero completions. That lab remains relevant to REST: first post-credential-change request intermittently resets on linux-arm64 #69's reset family, not here./ip/servicefield was blank for exactly that reason (console: waitForFinalPrompt can match the prompt redraw, so large replies return empty — blanks the #79 guest snapshot #109).Done-when
wwwblack hole is identified, or the discriminating experiment above returns a documented negative and this issue closes as "symptom fixed, mechanism not reproducible on current code".qemu.instructions.md), independent of whether it caused this.No regression test is owed here: #111 shipped the anchor test that keeps the reader honest, and B9's bisect is the regression evidence for this symptom.
Tracked in Wave 2 of #110 (B9, done). Correlated with #69 but not merged — see #69's own mechanism boundary. Historical evidence remains in the comments.