FABRIC-2.md §I.9: root-cause the terminal defect, not fixed (identify-only)
No code changed. This is a closure annotation on an existing open item where it lives (FABRIC-2.md, marked archival by CLAUDE.md, but §I's punch list landed there after the FABRIC series rename and genuinely still lives there) -- not new design content in an archival doc. Used QMP send-key to inject a real keypress into a running headless QEMU guest -- no physical keyboard needed, overturning this item's own "no way to observe the live QEMU GTK window from this environment" assumption. A first pass (amd64 + aarch64, real first keypress after a fresh boot, pre/post log captures) actually reproduced the defect but misread it as clean: both logs showed the echoed key on a bare new "[Hera]" line instead of appended to the still-visible "(zuse) ok> " line, which looks like normal REPL output unless you know the prompt should still be there. QMP screendump on aarch64 confirmed it visually. Root cause traced to two specific interacting lines, not guessed: console_ensure_line_start() (hal/console.c:312) emits its newline via the tx-count-exempt console_putc_inner(), by design, so idle chatter doesn't spam the REPL's prompt reanchor. But sk_repl_idle() (repl.c:178) calls it unconditionally on every ~1s idle beat, including at a bare prompt with nothing typed (n==0, repl.c:610) -- forcing a real but tx-count-invisible newline that the reanchor check (repl.c:633) never notices, so the prompt is never reprinted. The next real keystroke echoes onto the now-blank line with a lazily-emitted "[VMName] " prefix, indistinguishable from Enter having already been pressed. Not a first-keypress race -- it fires on any ~1s+ human pause at a bare prompt, i.e. nearly always, matching "consistent and reproducible." repl.c's own comment at lines 598-609 already half-diagnosed this exact failure mode and gates it for n>0 (mid-edit); the gap is the identical n==0 case (bare prompt) was treated as harmless. Likely fix location noted, not designed here. Verified reproducible on both amd64 (i8042) and aarch64 (virtio-input) input paths; riscv64 shares aarch64's virtio-input code path. Evidence: three fresh boot logs plus before/after screendump PNGs (evidence/aarch64/qemu-screenshot-20260905-013835-i9-*.png). No 3-arch acceptance boot run for this commit -- no code changed, and these logs/screenshots are themselves the evidentiary artifact, not a generic regression check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
dbaead0af2
commit
704573bdfc
+78
@@ -4638,3 +4638,81 @@ terminal emulator rendering the serial log stream, unrelated to kernel code at a
|
|||||||
moment of the first keypress after a fresh boot, ideally with the specific key pressed and
|
moment of the first keypress after a fresh boot, ideally with the specific key pressed and
|
||||||
architecture noted, so the byte sequence that actually reached the console can be read back
|
architecture noted, so the byte sequence that actually reached the console can be read back
|
||||||
directly rather than inferred from code review alone.
|
directly rather than inferred from code review alone.
|
||||||
|
|
||||||
|
**Update, 2026-09-05, first pass — SUPERSEDED by the second pass below, kept for the record
|
||||||
|
because the misread itself is instructive.** QMP's `send-key` command can inject a real key
|
||||||
|
event into a running QEMU guest headlessly — exactly the capture this item said it needed,
|
||||||
|
without a human at a physical keyboard. Procedure: boot to `(zuse) ok>`, capture the log tail
|
||||||
|
(confirming no artifact already present), then `send-key` one printable key (`a`), capture
|
||||||
|
again, then `send-key` `ret`. On both amd64 (`i8042_pop_scancode()`) and aarch64
|
||||||
|
(`virtio_input_pop_event()`), the raw log showed `a` followed by `UNKNOWN WORD: 'a'` and a
|
||||||
|
fresh prompt, which was read as clean — the defect was declared not reproduced, and the
|
||||||
|
remaining candidates named were QEMU's GTK input layer and the local terminal emulator, both
|
||||||
|
outside this repository.
|
||||||
|
|
||||||
|
**That reading was wrong. The defect was already sitting in those same logs.** Both captures
|
||||||
|
actually showed the echoed key on a **new, bare `[Hera]` line**, not appended to the still-
|
||||||
|
visible `(zuse) ok> ` line — e.g. `logs/20260905-012840/amd64/qemu-amd64-20260905-012840.log`'s
|
||||||
|
tail reads:
|
||||||
|
```
|
||||||
|
[Hera] (zuse) ok>
|
||||||
|
[Hera] a
|
||||||
|
```
|
||||||
|
not the expected `[Hera] (zuse) ok> a`. That line break, appearing before any character was
|
||||||
|
typed, *is* the reported defect — indistinguishable from Enter having already been pressed at
|
||||||
|
the bare prompt. It reads as "normal" unless you already know the prompt should still be on
|
||||||
|
that line, which is exactly the trap the first pass fell into. Confirmed visually too, via
|
||||||
|
QMP `screendump` on aarch64 immediately before and ~3s after a keypress:
|
||||||
|
`evidence/aarch64/qemu-screenshot-20260905-013835-i9-before-keypress.png` and
|
||||||
|
`...-i9-after-keypress-phantom-linebreak.png`.
|
||||||
|
|
||||||
|
**Root cause, traced to two specific lines, not guessed:**
|
||||||
|
1. `console_ensure_line_start()` (`hal/console.c:312`) calls `console_putc_inner('\n')` — the
|
||||||
|
variant that deliberately skips `g_console_tx_count++` (by design, per that function's own
|
||||||
|
doc comment: "so its format-only newline does not read as 'real output' to the REPL's
|
||||||
|
prompt re-anchor"). The newline is real (reaches `raw_putc()` and `vt100_putc()`) but
|
||||||
|
invisible to `console_tx_count()`.
|
||||||
|
2. `sk_repl_idle()` (`repl.c:178`) calls `console_ensure_line_start()` unconditionally, first
|
||||||
|
thing, every time it runs — including when it has nothing else to print.
|
||||||
|
3. `sk_console_readline()`'s idle branch (`repl.c:610`) calls `sk_repl_idle()` once
|
||||||
|
`SK_IDLE_BEAT_INTERVAL` (~1s at 100Hz, `repl.c:143`) has elapsed, gated only on `n == 0`
|
||||||
|
(nothing typed yet) — i.e. exactly the state of sitting at a freshly-printed bare prompt.
|
||||||
|
4. The reanchor check right after (`repl.c:633`, `console_tx_count() != prompt_tx_mark`)
|
||||||
|
never fires, because step 1's newline didn't move the counter it's watching. The prompt is
|
||||||
|
never reprinted.
|
||||||
|
5. `g_line_start` is now 1 (mid-console.c bookkeeping) with no prompt text on the new line.
|
||||||
|
The next real `console_putc()` — the actual echo, `repl.c:676` — sees `g_line_start` set
|
||||||
|
and lazily emits `[Hera] ` before the typed character (`console_putc_inner()`,
|
||||||
|
`console.c:280`). Net visible result: `[Hera] a` alone, exactly as observed.
|
||||||
|
|
||||||
|
Because the trigger is simply "one human-scale pause (~1s+) at a bare prompt with nothing
|
||||||
|
typed yet," this fires on essentially every prompt a person actually looks at before typing —
|
||||||
|
consistent with Captain Bob's own description ("consistent and reproducible"), not a rare
|
||||||
|
race. It is not really a "first keypress" bug; it's an idle-beat-vs-prompt-invariant bug that
|
||||||
|
happens to surface at the first keypress because that's the first character typed after the
|
||||||
|
silent break already occurred.
|
||||||
|
|
||||||
|
**This project's own comment already half-diagnosed this.** `repl.c:598-609`'s comment on the
|
||||||
|
`n == 0` gate explicitly describes this exact failure mode — an unconditional
|
||||||
|
`console_ensure_line_start()` "visually snapping the in-progress input line to a fresh (empty)
|
||||||
|
line -- indistinguishable from Enter having been pressed" — and gates it for `n > 0` (mid-edit,
|
||||||
|
characters already typed). The gap is that the identical forced break at `n == 0` (nothing
|
||||||
|
typed yet, sitting at a bare prompt) was treated as harmless, because a bare prompt "isn't
|
||||||
|
being edited" — but it's exactly where the tx-count exemption then hides the break from the
|
||||||
|
reanchor logic. The original author saw half of this problem; a fix likely belongs near that
|
||||||
|
same gate or the tx-count exemption in `console_ensure_line_start()`, not somewhere new.
|
||||||
|
|
||||||
|
**Secondary, unverified observation:** the framebuffer VT100 cursor glyph may also render
|
||||||
|
stale across this same event — `console_fb_draw_cursor()` is called after the prompt is first
|
||||||
|
printed (`repl.c:576`) and after a real character echoes (`repl.c:677`), but *not* after
|
||||||
|
`sk_repl_idle()`'s forced newline, so the drawn cursor block plausibly stays visually anchored
|
||||||
|
at the old end-of-prompt position until the next real keystroke redraws it. Consistent with
|
||||||
|
the "before" screenshot still showing the cursor glyph at the end of `(zuse) ok> ` even though
|
||||||
|
the underlying line-start state may have already flipped — but this was not traced through
|
||||||
|
`vt100.c`'s own cursor-rendering path, so record it as a plausible secondary symptom of the
|
||||||
|
same root cause, not a separately confirmed one.
|
||||||
|
|
||||||
|
**Not fixed — reported per this project's "identify, don't fix unless asked" rule.** The
|
||||||
|
diagnosis is complete and reproducible on demand (QMP `send-key` + a screenshot, no physical
|
||||||
|
keyboard needed, on any of the three architectures); whether and how to fix it is a decision
|
||||||
|
for whoever picks this item up next.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-09-05T05:24:05Z -->
|
<!-- Generated by mkcapsule --manifest 2026-09-05T05:38:13Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user