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.148 — Inbound and outbound BinkP still weren't confirming delivery to the hub (July 2026)

Reported live: even after v1.0b2.147's fix, the same Fidonet hub kept resending its entire backlog every poll — the file list never shrank, even across sessions ANetBBS logged as clean successes in both directions (hub polling in, and ANetBBS polling out).

  • FIX: ANetBBS only ever sent one BinkP end-of-batch signal (M_EOB) per session, sent before any files changed hands. Real file activity that followed voided it, and nothing ever sent a second, post-transfer confirmation — so a peer that never explicitly replies with its own end-of-batch signal (confirmed live: this hub doesn't) was left with no way to know the transfer was actually done, no matter how many files were individually acknowledged. Now sent unconditionally after every transfer completes, in both the inbound listener and the outbound poller.
  • FIX: the inbound BinkP listener's address announcement never checked the per-network domain override (ftn_domain), always deriving one from the network's display name instead — a long name like "ANotherNetwork" always announced as @anothern regardless of any override already configured.

8 new regression tests, each verified to fail without its corresponding fix.

v1.0b2.147 — Inbound BinkP sessions leaked a database connection every time (July 2026)

Reported live: a Fidonet hub kept resending the same packets dozens of times, even though ANetBBS was actually receiving and acknowledging every one of them successfully. A second sysop's own install hit the identical symptom.

  • FIX: every inbound BinkP connection opened its own database connection and never closed it — twice per connection, in fact. The leak accumulated over hours of polling from multiple networks, occasionally causing just enough delay to trip a peer's own session timeout, even though our side finished and logged the poll as a success. Connections are now explicitly closed after every session, success or failure.

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

v1.0b2.146 — Netmail flood was arriving through a second, unpatched path (July 2026)

Reported live: the same "Area Management Request"/"List of Available Areas" flood kept appearing every ~10 minutes even after v1.0b2.145 shipped, confirmed via direct database inspection.

  • FIX: the peer polls into ANetBBS's inbound listener independently of — and around the same cadence as — the outbound poll of the same hub. That's a separate code path (binkp_server.py) from the one fixed in v1.0b2.143/145 (poller.py, used when ANetBBS dials out), and it never had any content-based dedup fallback at all — only the exact-MSGID check, which the peer's fresh-MSGID-per-resend behavior always defeats. Both directions now share the same sender+subject+network dedup approach.

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

v1.0b2.145 — Web UI freezing for every user, and the netmail flood fix from v1.0b2.143 wasn't actually catching everything (July 2026)

Reported live, urgent: the entire web UI started freezing solid (unresponsive tab, not even a slow page) for minutes at a time, shortly after v1.0b2.143 shipped.

  • FIX: NetmailMessage.received_at had no database index, but v1.0b2.143's new content-based netmail dedup check filters on it. This app runs under eventlet, which does not make SQLite non-blocking — so an unindexed, growing scan on every inbound netmail blocked the entire process for every user on every page until it finished. Added the missing index, plus an automatic backfill so already-installed databases pick it up on next startup with no manual step required.
  • FIX: v1.0b2.143's dedup check also required an exact match on message body — confirmed live this wasn't sufficient (the same "Area Management Request"/"List of Available Areas" netmail kept creating new entries every ~10 minutes even with that fix deployed), which was also what fed the freeze above by continuously growing the unindexed table. Now matches on sender+subject+network within the dedup window instead, without requiring the body to be byte-identical.

If you already applied the manual CREATE INDEX workaround, this release's migration is idempotent and won't error on top of it.

9 new regression tests, each verified to fail without its corresponding fix.

v1.0b2.144 — Inbound BinkP: crashed sessions left zero diagnostic trace (July 2026)

Reported live: a peer sysop's own binkd log showed our inbound listener closing the connection abruptly, mid-transfer, with none of his files ever getting an acknowledgment from our side — but there was no corresponding ANetBBS-side record of what happened, anywhere a sysop could see it.

  • FIX: an inbound BinkP session only ever wrote a Poll Log entry on successful completion — that write happened as the very last step of handling a connection, so any exception before it (a network error, a bug, anything going wrong mid-transfer) meant the entire session vanished with no trace beyond a bare stack trace in the raw application log. A crashed session now writes an error-status Poll Log entry with the full frame-by-frame transcript captured up to the failure point, matching how the outbound poller has always behaved on failure.
  • Also: the Poll Log transcript viewer loaded scrolled to the top of a fixed-height box — for any real file transfer (hundreds of frame-by-frame lines), this made it easy to believe a transcript ended wherever the visible area happened to stop, when it actually continued below. Now auto-scrolls to the end (the diagnostically relevant part) on load, with a "jump to start" button for the handshake.

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

v1.0b2.143 — BinkP: peers resending their entire backlog forever, even after every file was correctly acknowledged (July 2026)

Reported live: a real FTN peer kept resending its complete inbound backlog on every single poll, indefinitely, despite ANetBBS correctly M_GOT-acknowledging every file every time — visible as a specific netmail ("Area Management Request" / "List of Available Areas") repeating every ~10 minutes.

  • FIX: a shared internal helper used by both the outbound poller (binkp.py) and inbound listener (binkp_server.py) opportunistically captures frames a peer interleaves while we're still waiting on our own acknowledgment — but it only recognized file-offer frames. A peer's own end-of-batch signal (M_EOB), which a spec-compliant mailer sends the instant its own outbound queue empties — independent of whether it's still waiting on us — fell straight through and was silently discarded, with zero trace. Our own end-of-batch handshake would then wait indefinitely for a signal the peer had already sent once and had no reason to resend unprompted. From the peer's own side, its session never registered as successfully completed (the binkp spec requires both sides to receive the other's end-of-batch signal), so it kept requeuing and resending everything on every subsequent connection — root-caused against a complete real session transcript and the official binkp protocol specification. Fixed on both the outbound and inbound sides: an early end-of-batch signal is now recorded and credited instead of waited for a second time.
  • Also added a content-based dedup fallback for inbound netmail: if a message's MSGID doesn't match anything on file, but its sender, subject, and body exactly match something received within the last 48 hours, it's now treated as a duplicate too — a backstop for any peer software that regenerates its MSGID on every resend.

15 new regression tests across three files, each verified to fail without its corresponding fix.

v1.0b2.142 — Echomail web preview didn't match what actually gets sent (July 2026)

Reported live: after the v1.0b2.139/141 origin-line fixes, a newly
composed message still showed no address in its "* Origin:" line when
viewed in the web UI — the fix looked like it wasn't working.

  • FIX: the message-read page displayed the raw stored origin_line
    field, which never includes the FTN address — that gets appended
    separately, per network, at actual BinkP send time. The preview
    simply didn't reflect what would really go out over the wire. The
    read page now computes the same address-append logic for outbound
    messages, so what a sysop sees in the web UI matches what a peer
    actually receives.

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

v1.0b2.141 — BinkP: stop re-sending the same netmail/echomail forever when a peer talks a lot before acking (July 2026)

Reported live: an AreaFix subscription request to a real SBBSecho hub
kept getting re-sent on every single poll, forever — the peer replied
fresh each time, since as far as it could tell it was receiving a
brand-new request every time.

  • FIX: the wait for a peer's M_GOT acknowledgment was bounded by a
    fixed count of frames (20), not wall-clock time. A peer that
    responds with substantial content of its own before finally
    acking — exactly what SBBSecho does, replying with real content
    spread across many small frames — could exhaust that budget with
    our own GOT never reached, even though the peer had already fully
    received and processed what we sent. The packet then got treated as
    unacknowledged and left queued for retry, so the next poll sent the
    identical request again — and the peer, having no reason to think
    otherwise, replied again. Now bounded by wall-clock time instead, so
    any amount of peer chatter before the real acknowledgment still gets
    fully drained.

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

v1.0b2.140 — Echomail origin line: show how to reach the BBS, not just its name (July 2026)

Follow-up to v1.0b2.139's address fix — requested live: the default
origin-line text itself should follow the common FTN sysop convention
of listing how to actually connect, not just the BBS's name.

  • ECHOMAIL_ORIGIN_LINE's default is now <hostname>, Telnet:N SSH:N HTTP:N (e.g. joesbbs.com, Telnet:23 SSH:22 HTTP:80), using
    whichever of those services are actually enabled and their real
    configured ports. Combined with v1.0b2.139's per-network address
    fix, a full origin line now reads e.g. joesbbs.com, Telnet:23 SSH:22 HTTP:80 (1:342/201) — matching the standard FTN format.

5 new regression tests.

v1.0b2.139 — Outbound echomail origin lines were missing the FTN address entirely (July 2026)

Reported live: outbound messages showed * Origin: ANetBBS - A Modern BBS System with no address at all, instead of the FTN convention
<text> (<our address>) a peer's own reply routing depends on.

  • FIX: the address-inclusive fallback only ran when the message's
    origin line was completely empty — but the web compose form always
    populates it from a single global tagline, so that fallback never
    actually fired for any real outbound message, on any network. The
    network's own address (already resolved per-network for multi-hub-
    identity installs) is now always appended, regardless of what origin
    text is configured.
  • Fresh installs also no longer default to generic ANetBBS-branding
    text for this — the origin line now defaults to the sysop's own
    configured BBS name instead.

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

v1.0b2.138 — Scheduled events: one hung handler could silently kill the entire scheduler forever (July 2026)

Reported live: a sysop's scheduled events — including the stock
defaults, not just the one they'd added — simply stopped firing
entirely, with no error anywhere. A 4-day log showed the scheduler's
own startup line exactly once and nothing after.

  • FIX: a handler that hung (a shell command waiting on input, a
    network call with no effective timeout, anything) froze the
    scheduler thread forever — silently, with no crash logged — taking
    down every other scheduled event with it, including ones that had
    nothing to do with whatever hung. This was a known, deliberate
    tradeoff ("a runaway is a sysop bug, not a security threat"), but
    the real-world consequence turned out to be worse than that framing
    accounted for. Every handler call is now bounded to a generous
    5-minute ceiling — comfortably above what any of the built-in
    handlers actually need — so one hung handler can no longer take
    every other event down with it.

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

v1.0b2.137 — BinkP inbound listener: stop making peers wait in silence (July 2026)

A peer sysop's own binkd log showed it delivering files to our
inbound listener successfully — every one individually acknowledged —
then sitting in total silence for over 2 minutes hearing nothing back
from us, before giving up, closing the connection, and marking the
whole transfer failed. It kept resending the same backlog on every
subsequent connection as a result.

  • FIX: the inbound listener used to fully drain everything a peer
    sent (waiting up to 120s for the peer's own end-of-batch signal)
    before ever sending our own outbound mail or announcing that we
    were done. If the peer's own mailer was itself waiting to hear from
    us first — which nothing in the protocol requires either side to
    do — neither side would say anything until somebody's timeout fired.
    We now send our own outbound mail (if any) and announce we're done
    immediately after authenticating, before waiting on the peer's
    stream at all.
  • As a consequence of sending earlier, a peer may now interleave its
    own file delivery while we're still waiting on an acknowledgment for
    ours — those were previously logged and silently dropped; they're
    now received correctly, same as the outbound side already handled.

5 new/updated regression tests, each verified to fail without the fix.

v1.0b2.136 — Docs/wiki full accuracy pass (July 2026)

First full docs+wiki pass since v1.0b2.45-46 — 5 parallel audit agents
checked all 29 docs/*.md files and all 47 wiki pages against actual
current code.

  • Fixed stale references to gunicorn (dropped for eventlet's native
    WSGI server back at v1.0a2.67, several docs never caught up), a
    badly outdated Service Control Center wiki page, and a factually
    wrong claim about echomail duplicate handling.
  • Documented several real, previously-undocumented features: a new
    Scheduled Events wiki page, ANetCRAFT (the bundled door game),
    the Ebook Reader, and the terminal Node Monitor's message capability.
  • Found and fixed 43 broken internal wiki links, plus assorted smaller
    path/port/version-number corrections.

No code changes — docs and wiki content only.

v1.0b2.135 — BinkP inbound listener: fix unbounded duplicate-message import (July 2026)

Reported live: a peer sysop's poll log showed the same ~570-message
backlog "received" fresh on every inbound BinkP connection, a few
minutes apart, with no sign of slowing down.

  • FIX: the inbound BinkP listener (a peer connecting into this BBS
    to deliver mail) imported every message in a delivered packet
    unconditionally — no deduplication at all. For echomail it didn't
    even capture the MSGID kludge onto the row in the first place; for
    netmail it captured MSGID but never checked for an existing one
    before inserting. The outbound poller's own import path already did
    both correctly (poller.py) — this second, separate import path
    (binkp_server.py, used specifically when a peer dials in rather
    than when we dial out) never got the same treatment. Any redelivery
    of a backlog — a peer's own retry logic, a flaky link, anything —
    got re-imported as entirely new messages, unbounded, every time.
    Now deduplicates by MSGID the same way the outbound path already
    does.

3 new regression tests, built from a real FTS-0001 packet round-trip
(not a hand-built dict), each verified to fail without the fix.

v1.0b2.134 — BinkP GOT-ack fix, Service Control Center + scheduled-events reliability (July 2026)

Three unrelated reports: a peer sysop's poll log showing a real
FidoNet hub rejecting our file acknowledgments mid-session, a Control
Center screenshot showing a terminal-services crash with an alarming
(but misleading) 99.3% CPU reading, and a peer sysop reporting his
nightly/hourly scheduled events just weren't firing.

  • FIX: receiving files during a poll we initiated ourselves (as
    opposed to a peer polling us) acknowledged each one with a bare
    GOT: filename — no size or timestamp. FTS-1026 defines M_GOT as
    filename size time, the same three fields as M_FILE; our own
    inbound listener already sent this correctly, but this second,
    separate code path never got the same fix. Tolerated by most peers,
    but confirmed live against a real, stricter binkd: the first two
    files' bare GOT acks were silently accepted, the third was rejected
    outright (ERR: M_GOT: cannot parse args) and the peer hung up —
    reproduced identically across two independent sessions.
  • FIX: the Service Control Center's CPU%/RAM/thread pills kept showing
    a dead service's last-known reading indefinitely, with nothing
    marking it stale — a crashed process could look like it was still
    pinning the CPU for up to 5 minutes after it actually exited. Now
    blanked to "—" the moment the sampler's most recent tick comes back
    empty. Same bug also silently inflated the aggregate CPU/RAM totals
    at the top of the panel.
  • FIX: the Control Center's own /status.json polling (systemctl
    state + journal reads, once per known unit, every 5s while the panel
    is open) ran raw subprocess.run() under gunicorn+eventlet — the
    same tight-loop shape already found crash-looping in the metrics
    sampler (v1.0b2.131, "Second simultaneous read on fileno N
    detected"). Routed through eventlet.tpool.execute() the same way.
  • FIX: one scheduled event with an out-of-range time value (the
    daily/weekly schedule kinds were never format/range-validated,
    unlike hourly/weekly-day/interval) could crash the entire event
    scheduler sweep every single tick — silently blocking every other
    scheduled event (nightly maintenance, log rotation, VACUUM, custom
    events) from ever running again, with only one generic log line to
    go on. The scheduler now isolates each event so one bad row can't
    affect the others, and the form validates daily/weekly times the
    same way the other schedule kinds already were.

17 new regression tests across the three testable fixes (the stale-
metrics display fix is front-end JS with no test harness in this
project), each verified to fail without its fix.