Skip to content

a bot by its name, a door that answers its own failure, an alarm that tries again, and a rollback that says what it does - #22

Merged
itswl merged 5 commits into
mainfrom
bots-errors-alarm-rollback
Oct 26, 2026
Merged

itswl merged 5 commits into
mainfrom
bots-errors-alarm-rollback

Conversation

@itswl

@itswl itswl commented Oct 26, 2026

Copy link
Copy Markdown
Owner

A second deployment running copies of these files met four things in a week that hold here too. Each fix below has a test that was run against the code without the fix and failed.

1. The watch scanner recognizes a bot by its name. WATCH_BOTS was the only way to drop a bot, so a bot nobody listed reached the digest as a message to answer. A sender whose display name has bot or robot as a word of its own, a CamelCase …Bot, or 机器人 is now dropped without being listed; bot inside a word (Talbot, Abbott, Botao) is a person. The list stays for bots whose names say nothing, and the compose comment beside it says so.

2. A door answers and records a failure nobody planned for. An exception a sidecar did not expect ended the handler thread: no answer, so the caller waited out its own timeout; no ledger line; and a traceback on stderr with no time.

  • The shared HTTP handler answers 502 and calls the door's ledger hook: call.failed with reason: unexpected <Exception> at the gate, signal.failed at the signer. An exception after the answer started sends nothing more.
  • The egress proxy refuses with 502 into its refusal ledger unless the tunnel was already spliced. A CONNECT name the resolver cannot even encode is one way to get there.
  • Every server logs an escaped error through the door's own logger, with the peer.
  • The proxy writes the request line it forwards back as Latin-1, the way it read it. UTF-8 turned every raw non-ASCII byte in a path into two; a test sends a raw UTF-8 path and checks the peer receives the same bytes.

3. An alarm tries a connection that did not happen again, in place. The investigator's, the pipe's and the judge's alarms posted once, so a moment without a resolver dropped the page and only a later failure sent another. The other deployment counted ten such misses in eleven days on the same kind of container network, each answered by a resend. Now: two more attempts, two seconds apart. An HTTP answer is the channel speaking and is not retried; a timeout is, at the price of a possible duplicate. The pipe and the judge stay inside their line ceilings (6,593 of 6,650 and 3,679 of 3,700).

4. The releasing page says what a rollback does with newer rows, and a release that changes a schema says it in its tag note. Read from the code for 0.8.0 back to 0.7.1: nothing fails to open; a silence a card scoped to one alert quiets its whole source until it expires; a judge intake row waiting at the rollback is not replayed until 0.8.0 is back.

Fixed while verifying: the forwarder's bind test, changed in 6ba9652 while clearing a CodeQL alert, clashed with a loopback socket. A loopback socket does not stop a wildcard bind with SO_REUSEADDR on macOS, so the local gate was red on a Mac while CI on Linux was green. It now clashes two forwarder binds, which fails on both and keeps the wildcard bind out of the test.

The decision note is .agents/notes/implemented/2026-10-26-a-bot-by-its-name-…. Nothing is deployed: the watch and sidecar changes reach a deployment at its next build, the alarm changes reach the published images at the next release.

Gate: STACK GATE GREEN locally; CI read back before merging.

itswl added 5 commits October 26, 2026 23:02
WATCH_BOTS was the only way to drop a bot, so a bot nobody listed reached the digest as a message to answer — what a second deployment running copies of these files saw twice in a week. A sender whose display name has bot or robot as a word of its own, a CamelCase ...Bot, or 机器人 is now dropped without being listed; bot inside a word (Talbot, Abbott, Botao) is a person. The list stays for bots whose names say nothing, and the compose comment beside it says so. Tested name by name both ways and through the scan itself; the scan test fails with the list-only filter.
… with its time and peer; the proxy forwards the request line byte for byte

An exception a sidecar did not expect ended the handler thread: no answer, so the caller waited out its own timeout; no ledger line; a traceback on stderr with no time. The shared HTTP handler now answers 502 and calls the door's ledger hook (call.failed with reason 'unexpected <Exception>' at the gate, signal.failed at the signer), and sends nothing more when the answer had already started. The egress proxy refuses with 502 into its refusal ledger unless the tunnel was already spliced. Every server logs an escaped error through the door's own logger, with the peer.

The proxy also writes the request line it forwards back as Latin-1, the way it read it: UTF-8 turned every raw non-ASCII byte in a path into two. A test sends a raw UTF-8 path and checks the peer receives the same bytes. Each test was run against the code without its fix and failed.
6ba9652 moved this test's clashing socket from the wildcard address to loopback while clearing a CodeQL alert, and its comment said the forwarder binds loopback; it binds the wildcard with SO_REUSEADDR, which on macOS succeeds beside a loopback socket. The local gate went red on a Mac while CI on Linux stayed green. The test now clashes two forwarder binds on one port, which fails on both, and keeps the wildcard bind out of the test.
…all three services

The investigator's, the pipe's and the judge's self-alarms posted once, so a moment without a resolver dropped the page and only a later failure sent another. A second deployment running copies of these files counted ten such misses in eleven days on the same kind of container network, each answered by a resend half a minute later. Now two more attempts, two seconds apart; an HTTP answer is the channel speaking and is not retried; a timeout is, at the price of a possible duplicate. The quiet window, the folded count and never-raises are unchanged. The pipe and the judge stay inside their line ceilings (6,593 of 6,650; 3,679 of 3,700). Each retry test fails without the retry.
A rollback runs older code over rows newer code wrote, so a release that changes a schema now says in its tag note what that does. The pipe and the judge migrate additively and read rows as dictionaries, so an older image opens a newer ledger; the investigator rebuilds case files field by field, so a field added to a run would make the release before it skip that file. Read from the code for 0.8.0 back to 0.7.1: nothing fails to open; a silence scoped to one alert quiets its whole source until it expires; a judge intake row waiting at the rollback is not replayed until 0.8.0 is back. The decision note for this whole change rides here.
@itswl
itswl merged commit 8045a14 into main Oct 26, 2026
12 checks passed
@itswl
itswl deleted the bots-errors-alarm-rollback branch October 26, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant