Restarting a dev session to change config is friction pretending to be safety. v0.13.0’s config watcher treats pyrpc.json as live state: save the file, and the session re-wires itself, including swapping the backend runtime entirely.
Diff parsed specs, not file bytes
The naive approach compares file contents. That fires on formatting churn, comment edits, editor atomic-save shims, all noise. Instead the watcher parses the new JSON into the same frozen dataclass used everywhere else and compares values:
new_spec = parse_backend(new_cfg) or spec new_clients = [c["root"] for c in clients_from_config(new_cfg)] backend_changed = new_spec != spec # dataclass eq, precise output_changed = new_client_dirs != client_dirs
Reordering keys, adding whitespace, fixing a typo in a comment: nothing happens. Changing entrypoint: restart. Adding a client root: codegen callback re-points, no restart. The distinction falls out of comparing the right representation.
Restart means resolve again
On backend change the watcher imports the new types module (failing loudly but keeping the old spec alive if resolution fails), then, only if pyRPC owns the process, terminate, wait, and relaunch through the same pure resolver used at startup. Attaching to an externally-run server? Config changes still re-wire codegen; we just never pretend to manage a process we don’t own.
The test was harder than the feature
Threads plus mocked generators plus shutdown sequencing is where tests go flaky. Three races surfaced while testing this feature, and each got a structural answer rather than a sleep:
- Two watchers, one writer. Both the Python-file and config watchers consume
watch(); a scripted generator mutating the fixture from both contexts corrupted the JSON mid-read. Fix: dispatch on watched paths so exactly one generator writes. - Shutdown eating the batch. If
stopset before the watcher processed its first yield, the change vanished. Fix: the fake keeps yielding batches forever, like watchfiles polling does, a dropped tick no longer loses the edit. - Detection vs teardown ordering. Fix: the mocked console blocks until the restart is observed, making session lifetime deterministic instead of racing thread scheduling.
The result passes across repeated full-suite runs with zero timing sensitivity. Flaky tests are rarely about the code under test, they are about unowned nondeterminism in the harness. Own it explicitly, or delete the test.

pyRPC