ad30f93f77
A user's log from the real target device showed nsenter'd `ip addr add ... dev eth0` failing with "Cannot find device" immediately after network_tap_relay.h's relay had already created it -- confirmed by hand that retrying the whole session a few times eventually worked. join_one_network() now retries (bounded, ~500ms, quiet until final success/give-up) the three steps that touch the just-created container interface -- IPv4 address, IPv6 address, bringing it up -- via a new run_with_retry() instead of plain run(). A tempting first fix was investigated and ruled out by direct A/B testing, not just reasoned about: having the relay itself self-verify the device is visible (a same-process check, immediately after creating it, before ever reporting success) was tried first, in relay_child_main(). It made things categorically worse: the container-side device became permanently invisible to every external nsenter afterward, 100% reproducibly (confirmed with a 10-second retry budget -- never once became visible), on a mechanism that had otherwise worked correctly and instantly, zero retries needed, on every real session tested earlier the same day -- including a from-scratch self-test reproduction that had passed reliably many times before this one change, and immediately went back to passing once it was reverted. Root cause not fully understood (something about forking a subprocess that inherits the tap fd -- deliberately not O_CLOEXEC -- while still holding it open, immediately after device creation, appears to corrupt the device's external visibility on this kernel specifically), but the fix is unambiguous: never add an internal, same-process/fd-holding self-check to the relay; only the external, separate-process retry is safe. network_tap_relay.cpp ends up completely unchanged -- the actual fix lives entirely in network_join.cpp's own retry. self_test.cpp's own container-visibility check needed the same external retry treatment, for the same underlying reason. Verified as root via the doas rule: three separate real --no-veth sessions all succeeded getting eth0 on the first attempt (no retries triggered), and the self-test passes reliably across repeated runs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz