Wires Claude Code to a didbot personal data server: hooks, a plugin, and the harness contract they rest on.

fix(collection): stop the footer drawing from a signal handler #64

Merged permadeath.com
targeting main
Merged opened by and assigned to permadeath.com
targeting main

The receiver died with RuntimeError: reentrant call inside <_io.BufferedWriter name='<stdout>'>. Its SIGWINCH handler measured the terminal and flushed stdout, and a second SIGWINCH arriving while that flush was still blocked on the terminal landed inside it. Dragging a window edge sends a stream of SIGWINCH rather than one, so this is not a rare race.

No handler writes to stdout now. SIGWINCH records that the size changed and SIGUSR1 records that a summary was asked for; the accept loop measures, draws and prints on its way round, and the listening socket takes a short timeout so it comes back around on a quiet machine.

Below is a harness that runs the receiver on a deliberately slow pty, floods it with SIGWINCH and feeds it records at the same time. exit 120 is CPython's code for a shutdown that could not flush stdout, which is this crash arriving too late for its own traceback to survive.

$ storm.py <the receiver from main>
signals 23525   DIED (exit 1)   reentrant tracebacks: 1
signals 29574   DIED (exit 120)   reentrant tracebacks: 0
  File "tools/echo-receiver.py", line 262, in <lambda>
    signal.signal(signal.SIGWINCH, lambda *_: self._measure())
  File "tools/echo-receiver.py", line 272, in _measure
    sys.stdout.flush()
RuntimeError: reentrant call inside <_io.BufferedWriter name='<stdout>'>

$ storm.py tools/echo-receiver.py
signals 959769    SURVIVED   reentrant tracebacks: 0
signals 1005908   SURVIVED   reentrant tracebacks: 0
signals 1216061   SURVIVED   reentrant tracebacks: 0
signals 1394640   SURVIVED   reentrant tracebacks: 0
signals 1225687   SURVIVED   reentrant tracebacks: 0

🤖 Generated with Claude Code

This pull request has no versions submitted yet.