Skip to content

Fix webhook listener stalling (hopefully) - #144

Draft
dgw wants to merge 3 commits into
masterfrom
webhook-stalling
Draft

Fix webhook listener stalling (hopefully)#144
dgw wants to merge 3 commits into
masterfrom
webhook-stalling

Conversation

@dgw

@dgw dgw commented Aug 25, 2026

Copy link
Copy Markdown
Member

I wanted to see if we can avoid, or at least greatly reduce, instances of our Libera bot's webhook listener becoming unresponsive. This is the result of asking VS Code a question, telling it not to change so much at once, and researching the eventual patch myself before committing it. A second look from another human would be awesome, before testing this "in production" on our Libera bot for a while (weeks or months; it's an infrequent and intermittent issue) before considering a merge to the main branch.

The end result is still disclosed as Claude Sonnet 5–assisted in my final diff, because I believe in FULL transparency regarding when "AI" was used.

The "real" patch is sandwiched by housekeeping changes. I'm not sure whether there's such a thing as a "webhook sandwich", but maybe I just invented it.

dgw added 3 commits August 25, 2026 18:19
The most notable thing here is removing a `sys.version_info.major` check
that we no longer need (Sopel 8 already requires Python >=3.8, and the
plugin's requirements now specify sopel>=8).

It must have been a while since anyone opened this in an IDE with import
linting, though. All I did was look for dimmed/grayed-out imports after
opening the project in VS Code today.
On a whim, I decided to see if VS Code's built-in copilot chat could
help figure out our intermittent issue with the webhook server sometimes
becoming unresponsive.

I briefly summarized the problem and gave it a direction to investigate
(`wsgiref` and its basic server implementation). The copilot chat chose
Claude Sonnet 5 to help with this, and initially it spat out a whole
litany of changes to this section of the code adding multi-threading
support to the server on top of the timeout.

I asked it to scale back and just try the timeout for now, "one thing at
a time", then tweaked the output and fenced it with the comments marking
exactly what section I let the matrix-multiplication bot touch.

The disclaimers are included out of a perhaps-unreasonable desire for
transparency. I followed the imports myself to verify that there is
indeed a `timeout` to set somewhere up the tree, but I feel it's fair to
explicitly mark that this approach originated from an LLM.
Python 3.10 deprecated this method of setting a Thread object's daemon
status. It is allowed to set the `Thread.daemon` property directly
before the Thread has been started, but since there's also a `daemon`
kwarg available (in Python 3.8+; I didn't go back further than Sopel 8's
supported range) let's just use that when creating the Thread.
@dgw dgw added the wip label Aug 25, 2026
@dgw dgw mentioned this pull request Aug 25, 2026

@SnoopJ SnoopJ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change itself looks fine, I mostly had thoughts about how provenance is being indicated.

Comment thread sopel_github/webhook.py
Comment on lines +89 to +91
# BEGIN Claude-assisted tweaks (Sonnet 5, VS Code auto-selected)
# wsgiref never times out reads, so a stalled/half-open client
# connection can hang the server indefinitely without this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feelings about the way provenance is being indicated here. On the one hand, it's laudable to track where the code came from (or rather, the idea for the change, since the actual amount of code here is marginal). On the other hand, half of this commit is that kind of commentary!

I think I would be happier without the BEGIN/END, but rather noting that an LLM assisted and pointing to this PR in the comment right by the timeout.

Not a strong opinion, but I thought I'd share how I feel about the practice given here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to fence off the area Claude touched, including its tweaks to how we set the request handler class. You really think it's too much?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea it feels like duplicating the information in the commit itself a little bit, especially when I consider what such a thing would look like over time if LLM tools were touching the same bit of code, and you ended up with overlaps (which seems likely, since they help create mistakes all the time).

Not a strong set of opinions, but maybe I'm underestimating how much of the lifting the tool did here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My edits were pretty minor, so I didn't want to claim credit for authoring the changed lines. I guess you could argue that once I double-checked Claude's work against docs and stdlib source code, and decided that even without the tool I would've arrived at ~the same patch, the fencing isn't really relevant any more?

For this patch I obviously skewed really heavily toward making it obvious which part of the code was touched by matrix multiplication, without even having to look at the commit/blame log. That works for small, isolated tests of those tools such as this. Worth rethinking if any of us keep using them on this codebase, but I suspect that won't happen. After another, green-field, trial drafting something else, I have decided that the best use of those tools is to have them suggest something, then read it for inspiration before rewriting from scratch. Then the tool helps me learn about interfaces I wasn't familiar with, and I still fully understand the end product.

(Going a wee bit off topic, aren't I? But I still think it's worth saying, so I'll leave it be.)

Comment thread sopel_github/webhook.py
Comment on lines +92 to +97
handler_class = self.options.pop('handler_class', WSGIRequestHandler)
if self.quiet:
class QuietHandler(WSGIRequestHandler):
class QuietHandler(handler_class):
def log_request(*args, **kw):
pass
self.options['handler_class'] = QuietHandler
handler_class = QuietHandler

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The multiple assignment here makes me raise my eyebrows a little bit, but since it's gated behind a flag where we add a wrapper, I think it's okay to leave it as is.

@dgw dgw Sep 2, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What pattern would you use if not this?

I'm in favor of the change to make specifying the handler class more explicit even on the default path (when not self.quiet).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I were going to change it to avoid the multiple assignment, I'd probably name the first assignment base_handler_class, then have a trivial assignment (handler_class = base_handler_class) in the non-subclass case.

Alternatively, one might duplicate the assignment to self.options instead (i.e. self.options['handler_class'] = QuietHandler on one branch and the base-case on the other).

But I think as you've written it, it's clear enough. This is a case where multiple assignment just kinda is the best way to write it, especially since we pop()ed the original.

Comment thread sopel_github/webhook.py
Comment on lines +98 to +100
# Claude defaulted to 60 seconds; dgw thinks 10 is more reasonable for
# our webhook listener since GitHub itself will give up pretty quickly.
handler_class.timeout = 10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it could be a configurable option, but that's probably overthinking things. What you have looks fine to me.

@dgw

dgw commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

Have deployed this to a test branch on the Sopel instance serving our channels on Libera, as a copy of master + git merge origin/webhook-stalling. Given how intermittent the issue is, we should allow O(weeks) for shakedown before declaring that this likely fixed the timeouts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants