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 withRuntimeError: 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 calledsubprocess.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
LOGOFFevery 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.LOGOFFis no longer sent on an
individual leave (neither an explicit/quitnor 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/CHATTERSlisting
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, taggedMRC RAW OUT/MRC RAW IN,
atDEBUGlevel only — gated behind a newMRC_BRIDGE_LOG_LEVEL
environment variable (defaults to the existingINFO, 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 "emptytoRoomfor 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~, withtoRoom
populated. Reverted to populate it, keeping the empty-toRoom
exception only forIDENTIFY/REGISTER/UPDATE, which the spec
documents separately. - FIX: same correction for
LOGOFF(sent every time you leave) — the
documented template has bothfromRoomandtoRoompopulated
with the room name;.124had emptiedtoRoombased on the same
wrong assumption. Reverted to match spec. - FIX: a genuinely new finding from the real spec —
USERIPshould
send an emptyfromRoom(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
LOGOFFpacket sent every time you leave MRC populated the
toRoomfield with the room name; the reference client always sends
it empty forLOGOFF. 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/UPDATEsent an emptytoRoom; 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/identifyon
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-Forsince 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 aROOMTOPIC: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 todocs/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.