ANetBBS Changelog — Beta History (pre-v1.0.0)

Archived history of every internal beta build number (v1.0a1.1 through
v1.0b2.239) from before ANetBBS's v1.0.0 full release in August 2026.
Preserved as-is — a real record of what changed at each step during
development — but split out of the main CHANGELOG.md
once it passed 6,000 lines, to keep that file scannable for the
v1.0.0-onward history that actually matters day to day. Newest-first,
same as the main changelog.

v1.0b2.133 — Terminal MRC: fix false "Rate limit" error on split messages (July 2026)

Reported live ("still getting this rate error when dming, even
though it's not [too long]"). Not actually related to message length
in the sense reported — the fix is real, but the mechanism is
different from what it looked like.

  • FIX: a message that needs to split into multiple wire chunks (this
    can happen well under the 140-char hub limit — a decorated display
    handle's prefix/suffix eats into that budget before your own text
    even starts) was sending every chunk back-to-back with zero delay.
    The bridge only allows one message every 0.5 seconds per
    connection, so any chunk after the first was always instantly
    rejected with "Rate limit: please slow down." Fixed by adding a
    small pause before each chunk after the first, in all four places
    a message can split: room chat, /me, /broadcast, and DMs
    (/msg, /r).

4 new regression tests, each verified to fail without the fix.

v1.0b2.132 — Web: fix door game output silently stopping after the first idle pause (July 2026)

Root-caused the actual "dosemu2 door games show a black screen over
the web UI" report — confirmed via a live log capture showing dosemu2
booting and rendering correctly server-side the entire time, while
the browser never received any of it.

  • FIX: handle_start_game()'s background output-draining task has no
    Flask app context of its own. Its idle-timeout check (does the
    queue sit empty for 5+ seconds? then verify the session is still
    active) needs a database query, which needs that context — and
    didn't have one. The very first time a door produced no output for
    5+ seconds (a completely normal boot/loading pause — near-
    guaranteed for dosemu2 specifically, since it's much slower to boot
    than a native door), that query crashed with RuntimeError: Working outside of application context, which the surrounding code treated
    as "the session must be gone" and permanently stopped draining the
    queue for the rest of that browser session. The backend kept
    running the door correctly the whole time; nothing it rendered
    after that point ever reached the browser again — exactly a stuck
    black screen.

1 new regression test that reproduces the real 5-second idle gap
directly (not simulated) and confirms output sent after it still
reaches the browser — confirmed to fail with the exact live error
message before the fix.

v1.0b2.131 — Web: fix Service Control Center metrics sampler crash-loop (July 2026)

Found while investigating a separate live "dosemu2 door games show a
black screen over the web UI" report — this was the actual thing
crash-looping in the same process, every ~2 seconds, confirmed via
journalctl.

  • FIX: the Service Control Center's background per-PID metrics
    sampler called subprocess.run() once per known systemd unit in a
    tight sequential loop. Under gunicorn+eventlet, that call runs
    through eventlet's greened subprocess module; any transient hiccup
    could leave its fd-listener registered in the shared epoll hub past
    the point its fd number got recycled by the next call, colliding
    with it (RuntimeError: Second simultaneous read on fileno N detected) and crash-looping from that point on, indefinitely. Now
    dispatches the call to a real native OS thread via
    eventlet.tpool.execute(), bypassing eventlet's greened machinery
    entirely — the officially recommended pattern for exactly this.

4 new regression tests, each verified to fail without the fix.

v1.0b2.130 — MRC: stop sending LOGOFF on individual leave — this is what was breaking trust (July 2026)

Root-caused from a real, complete packet transcript
(MRC_BRIDGE_LOG_LEVEL=DEBUG) captured on the live server — the first
fix in this saga backed by actual evidence instead of spec/source
comparison.

  • FIX: the bridge sent LOGOFF every time an individual caller left
    a room. The transcript showed this ends the hub's MRC Trust state
    for that handle immediately — the very next join got "Cannot
    join ROOM, please IDENTIFY to use this handle" even though the
    bridge's own connection to the hub never dropped in between. Since
    this bridge holds one persistent shared connection to the hub per
    BBS install across every local caller's join/leave, there's no
    need to tell the hub "this handle is logging off" the way a
    single-session client would. LOGOFF is no longer sent on an
    individual leave (neither an explicit /quit nor an abrupt
    disconnect) — NOTME's "has left chat" message still covers the
    visible room-presence announcement other users see.
  • Trade-off worth knowing: the hub's own /who/CHATTERS listing
    may show your handle lingering briefly after you leave, until your
    next reconnect's fresh join or the hub's own idle timeout cleans
    it up.

4 new regression tests, each verified to fail without the fix.

v1.0b2.129 — MRC: fix debug-level packet tracing not actually activating (July 2026)

.128's MRC_BRIDGE_LOG_LEVEL=DEBUG had no effect — confirmed live,
zero MRC RAW lines after enabling it and restarting.
logging.basicConfig() is a documented no-op if the root logger
already has a handler attached before it runs (plausible under
systemd, depending on import order) — it was silently doing nothing.
Now sets the level directly on the mrc_bridge logger itself, which
takes effect regardless.

2 new regression tests (one specifically simulating the pre-existing-
root-handler scenario that caused this), verified to fail without the
fix.

v1.0b2.128 — MRC: add full raw packet tracing for diagnosing the identify-persistence bug (July 2026)

Three rounds of wire-format fixes verified against reference client
source and the actual official protocol spec haven't resolved the
live "still have to identify every time" report — rather than guess a
fourth time, this adds a way to capture a complete real transcript for
direct comparison.

  • Every outgoing packet (send_packet) and every incoming line from
    the hub are now logged verbatim, tagged MRC RAW OUT/MRC RAW IN,
    at DEBUG level only — gated behind a new MRC_BRIDGE_LOG_LEVEL
    environment variable (defaults to the existing INFO, unchanged).
    Deliberately not on by default, since full tracing would otherwise
    mean every private chat message lands in plaintext in the server's
    own logs permanently.

1 new regression test, verified to fail without the fix.

v1.0b2.127 — MRC: correct wire format against the actual official protocol spec (July 2026)

Direct follow-up to .123/.124, after obtaining the real MRC
protocol developer documentation (not inferred from client source).
Two important corrections:

  • FIX: .124's "empty toRoom for every generic command" change was
    based on one reference client's own implementation shortcut, not
    the actual spec — the documented template for most commands
    (MOTD, WHOON, BANNERS, etc.) is
    user~bbs~room~SERVER~msgext~room~COMMAND~, with toRoom
    populated. Reverted to populate it, keeping the empty-toRoom
    exception only for IDENTIFY/REGISTER/UPDATE, which the spec
    documents separately.
  • FIX: same correction for LOGOFF (sent every time you leave) — the
    documented template has both fromRoom and toRoom populated
    with the room name; .124 had emptied toRoom based on the same
    wrong assumption. Reverted to match spec.
  • FIX: a genuinely new finding from the real spec — USERIP should
    send an empty fromRoom (user~bbs~~SERVER~msgext~~USERIP: ipaddress~), which the code never did correctly in any prior
    version. Fixed.
  • Also confirmed from the spec: MRC Trust is keyed to (Handle, BBS
    Name, BBS's own IP address) with a 30-day window — not the
    individual caller's IP. Confirmed the BBS has a stable static IP,
    ruling that out as a contributing factor, which puts the weight
    back on these wire-format corrections actually being the fix.

10 new/updated regression tests, verified against the reference spec,
each confirmed to fail without the fix.

v1.0b2.126 — Terminal: bulletins now use the scrollable ANView reader (July 2026)

  • FEATURE: reading a bulletin longer than a page used the old
    page-break [MORE] pager instead of the scrollable ANView reader
    already used for echo/private messages. Now uses ANView (Up/Dn/
    PgUp/PgDn to scroll, Q to back out) instead.
  • Bulletins are authored via the web admin's plain-text form, not
    composed at a terminal, so this deliberately does not reuse the
    message reader's CP437-mojibake decode pipeline — that would have
    silently corrupted any non-ASCII bulletin text (curly quotes, em
    dashes, accented letters) into unrelated glyphs. Raw ANSI escapes
    in a bulletin still render correctly.

3 new regression tests, verified to fail without the fix.

v1.0b2.125 — Terminal: file areas now lightbar-scrollable, matching message areas/RSS (July 2026)

  • FEATURE: the terminal "File Library - Areas" screen used to dump
    every configured area top-to-bottom with a plain number-entry
    prompt — for any sysop with more file areas than fit one screen,
    this meant relying on the terminal client's own scrollback just to
    see the top entries. Now uses the same arrow-key lightbar selector
    already used for message areas and the RSS reader: Up/Dn/PgUp/PgDn
    to scroll, Enter to open, A for All Files, Q to back out.

3 new regression tests, verified to fail without the fix.

v1.0b2.124 — MRC: fix real LOGOFF/command wire-format mismatch behind repeated forced re-identify (July 2026)

Direct follow-up to v1.0b2.123, after live testing showed identify was
still required on every leave/rejoin. Root-caused by comparing exact
packet fields against the reference client's C source, field by field.

  • FIX: the LOGOFF packet sent every time you leave MRC populated the
    toRoom field with the room name; the reference client always sends
    it empty for LOGOFF. If the hub's trust/session tracking keys off
    that field, a populated one on logoff would plausibly end a handle's
    trusted session — a direct, verifiable candidate for "have to
    identify every time."
  • FIX: the same mismatch existed on every other generic command
    (MOTD, WHOON, CHATTERS, USERIP, etc.) — previously only
    IDENTIFY/REGISTER/UPDATE sent an empty toRoom; the reference
    sends it empty for all of them.

7 new regression tests verified against the reference wire format,
each confirmed to fail without the fix.

v1.0b2.123 — MRC: fix "have to /identify every time" root cause + topic/userlist bugs from a full client review (July 2026)

Prompted by a report that MRC Trust never seemed to persist, plus two
bugs found live while testing. Root-caused by comparing ANetBBS's
terminal MRC client against the wire protocol line-by-line.

  • FIX: the protocol expects a USERIP: packet on every room join, and
    the hub appears to use it to recognize a returning already-identified
    connection — but ANetBBS's bridge never sent one, for any user, on
    either the terminal or web client. This forced a fresh /identify on
    every single connect, no matter how recently the same handle had
    already identified. Now sent correctly: the web client's real address
    is captured server-side from the incoming connection (honoring
    X-Forwarded-For since the bridge sits behind nginx), and the
    terminal client — whose connection to the bridge always looks like
    localhost — now reports its own real caller address explicitly.
  • FIX: the room topic line (── Topic: ...) was re-printed above the
    input line every time the hub re-sent a ROOMTOPIC: packet, even
    when the topic text hadn't actually changed — spamming the same line
    repeatedly. Now only announced when the topic genuinely changes.
  • FIX: the sidebar user list only ever added names, never removed
    them — so if a single user's leave notice was ever missed, their
    name stuck around forever, since even a fresh full list refresh from
    the hub couldn't clean it up. Now treated as the authoritative
    snapshot it is: a fresh list fully replaces the known set.
  • Reviewed CTCP handling, DM/reply, room switching, and away/back
    against the protocol's actual wire behavior — all already correct,
    no further changes needed there.

12 new regression tests, each verified to actually catch its bug
(reverted and confirmed failing, then restored).

v1.0b2.122 — ANetIRC: fix broken function/nav keys on SyncTerm + 4 more real bugs from a deep review (July 2026)

Prompted by a bug report ("F2 doesn't remove the user listing") from a
SyncTerm/SSH user. Root cause was much bigger than F2 alone.

  • FIX: the key parser was built entirely around xterm's keyboard
    conventions, but SyncTerm — likely the most common BBS terminal —
    uses a completely different, non-standard set of codes, confirmed
    directly against SyncTerm/CTerm's own official documentation. As a
    result F1 through F12, PgUp, PgDn, End, Insert, and Back Tab all
    silently did nothing
    for SyncTerm users, not just F2. Rewrote the
    parser to correctly handle SyncTerm's real sequences while keeping
    existing xterm/vt220 support working for other clients.
  • FIX: CTCP requests other than ACTION (VERSION, PING, etc.) used to
    leak into the chat window as a blank ghost line from that user, and
    the requester never got a reply (real IRC etiquette expects one;
    some bots/clients flag nicks that never answer). VERSION and PING
    now get a real reply and don't show in chat; anything else is
    silently ignored instead of leaking a blank line.
  • FIX: nick tab-complete could never actually cycle to a different
    match on repeated Tab presses — the second press looked for a match
    in the already-completed text, found none, and silently did
    nothing. Now correctly cycles through every match.
  • FIX: a single malformed line from the IRC server could silently
    kill the background connection with zero visible error — chat would
    just stop receiving anything, indistinguishable from a hung
    connection. Now logs a visible error and keeps going.
  • FIX: a literal | character typed into a bookmark field (most
    plausibly the label, e.g. "Home | Personal") silently corrupted and
    misaligned every field after it the next time bookmarks loaded, with
    no error. Fixed at the point bookmarks are saved.

31 new regression tests, each verified to actually catch its bug
(reverted and confirmed failing, then restored) rather than just
passing by coincidence.

v1.0b2.121 — BinkP inbound transcripts; FileFix docs; Ask Anet troubleshooting content (July 2026)

  • FIX: inbound BinkP sessions (a peer connecting TO this BBS) never
    saved a frame-by-frame transcript — only outbound polls did, since
    v1.0b2.47. This mattered most for exactly the direction this
    session's whole BinkP audit was chasing: a peer hub pushing mail in
    and then stalling, with no in-app log to look at afterward. Inbound
    sessions now save a transcript the same way outbound polls do,
    viewable in the same Admin → Echomail Networks → Poll Logs UI
    (no UI changes needed — it already just checks whether a log row
    has one).
  • DOCS: FileFix (the file-echo counterpart to AreaFix) was never
    documented anywhere, despite being a fully-working bot — added a
    full section to docs/07-file-areas.md.
  • FEATURE: Ask Anet's help search only ever indexed the wiki, not
    docs/*.md — and the wiki had zero troubleshooting content, so
    searching for help with a real problem always came back empty.
    Added a comprehensive Troubleshooting wiki page consolidating real
    symptom→fix entries from across the docs (web/nginx, FTPS, MRC,
    BinkP/echomail, doors, Docker, webhooks, scheduled events, MSP,
    Raspberry Pi, RSS), linked from Home and the Sysop Guide, and
    verified end-to-end that Ask Anet's search now actually finds it.

v1.0b2.120 — BinkP/echomail: close 3 remaining alignment gaps found in follow-up spec research (July 2026)

Direct follow-up to v1.0b2.119's BinkP audit — closes three specific
gaps identified after that release, each now verified against a real,
fetched primary source rather than inference:

  • FIX: the outbound BinkP client never cross-checked a peer's claimed
    address (M_ADR) against the hub address we actually dialed — a
    wrong host answering on the expected IP/port (stale DNS,
    misconfiguration) would sail through unnoticed as long as it also
    had our password. Now logs a clear warning on mismatch (the
    password remains the real auth gate, so this doesn't abort the
    session — a legitimate multi-AKA hub could otherwise trigger a
    false positive and break a working link).
  • FIX: nodelist header parsing verified directly against the actual
    FTS-5000 spec text (fetched and confirmed, not guessed) — the real
    example given in the spec now round-trips correctly, in addition to
    this software's own generated format from v1.0b2.119.
  • VERIFIED (no bug found): re-checked the QWK MESSAGES.DAT
    message-header field layout against the actual published QWK format
    spec, byte by byte. Everything already matched — added dedicated
    regression tests built independently from the spec's own offset
    table so this can't silently drift later.

v1.0b2.119 — BinkP: fix a real mail-loop/mail-loss bug, full-subsystem audit (July 2026)

Prompted by a real peer sysop's report of repeated duplicate bundle
deliveries and mid-session disconnects. Root-caused against the FTS-1026/
FTS-1027 specs, binkd's own C source, and Synchronet's binkp.js reference
implementation, then verified end-to-end with new regression tests
(scripted real BinkP frames, not mocks).

  • FIX: inbound listener could freeze every other concurrent BinkP
    connection
    while importing a large batch of mail — the listener
    runs one shared event loop for all sessions, and importing (DB
    writes, ZIP extraction, parsing) ran as blocking code directly on
    it. A big catch-up on one connection could stall a brand-new,
    unrelated connection for minutes, causing the peer to give up before
    ever getting an acknowledgement — the direct cause of the reported
    loop. Import now runs on a background thread so it can't block other
    sessions.
  • FIX: the inbound listener could also delay closing its own session
    until after importing what it just received; a slow import meant the
    peer's connection timed out before the session formally ended, so
    the peer's own bookkeeping never marked those files delivered and
    resent them next time. The session now finishes (and the socket
    closes) immediately after files are received/acknowledged, before
    import runs.
  • FIX: the outbound client used to mark a batch of messages "sent"
    regardless of whether the hub actually acknowledged it — a busy or
    unstable hub replying "skip" or "error" (both normal, spec-legal
    responses) silently and permanently discarded real outbound mail
    with no retry. It now only marks messages sent on actual
    acknowledgement; anything else is safely retried next poll.
  • FIX: the outbound client didn't handle the hub closing the
    connection while waiting for that acknowledgement — it would crash
    instead of retrying gracefully.
  • FIX: a path-traversal gap in both the inbound listener and outbound
    client — an inbound filename was never reduced to a safe basename
    before being used to write a local file.
  • FIX: two silent-failure spots (a malformed FTN address, corrupted
    routing history on a message) now log a warning instead of failing
    invisibly, so a sysop debugging misrouted mail has a trail to follow.

Follow-up full audit of the rest of the echomail subsystem (AreaFix/
FileFix, the hub tosser, QWK-hub FTP, InterBBS sync, nodelist import)
surfaced several more real issues, now also fixed:

  • FIX (security): inbound AreaFix/FileFix netmail was never checked
    against the AreaFix password — any netmail addressed to the areafix/
    filefix robot could subscribe/unsubscribe echo areas with no
    authentication at all. Now verified against the same password the
    outbound side already sends (FTS-0024), on both the leaf and hub
    paths.
  • FIX: the hub's SEEN-BY loop-prevention check never actually worked
    (comparing a zone-qualified address against untokenized raw SEEN-BY
    lines, which could never match) — a hub could re-toss a message right
    back to the exact node that's also subscribed to receive it, a
    needless bounce and a contributor to loops.
  • FIX: a single bad message in a multi-message QWK REP upload could
    silently discard earlier, already-successfully-imported messages from
    the same batch.
  • FIX: InterBBS Wall/Last-Callers/Game-Scores sharing was re-sending a
    redundant AreaFix subscribe request on every single post/caller/score
    event instead of only when actually needed.
  • FIX: nodelist header parsing (day-of-year, release date) never worked
    against this software's own generated nodelists.