Skip to content

fix(dig-app): a control-plane test hangs forever on Windows, so the local suite can never finish #222

Description

@MichaelTaylor3d

Task

cargo test -p dig-app-core --lib cannot complete on a Windows dev machine. One test hangs
indefinitely and takes the whole harness process down with it, so a developer or lane running the
local suite gets no result at all — not a failure, a hang. Make the local suite terminate on Windows.

Context

crates/dig-app-core/src/control.rs:1160, a_user_configured_host_off_loopback_still_receives_the_token:

let listener = std::net::TcpListener::bind(("0.0.0.0", 0)).expect("bind");
let port = listener.local_addr().expect("addr").port();
let _ = post_json_to("0.0.0.0", port, b"{}", Some("super-secret-control-token"),
                     Duration::from_millis(300), EndpointTrust::UserConfigured);
let (mut stream, _) = listener.accept().expect("the configured node must be dialled");
let mut request = Vec::new();
stream.read_to_end(&mut request).expect("read the request");

Neither accept() nor read_to_end() carries a timeout, and read_to_end returns only at EOF —
i.e. only once the client end is fully closed. The 300ms budget passed to post_json_to bounds the
client's wait, not the server side's. Dialling the wildcard address 0.0.0.0 is also
platform-dependent: Linux routes it to localhost, Windows does not treat it the same way.

Measured on Windows 11, on a tree whose control.rs blob is byte-identical to main
(a13aa3033042ae0f4954282b252c4a53ab595140), so this is pre-existing and not introduced by any
branch:

  • In a full --lib run: has been running for over 60 seconds, then
    test exited abnormally and cargo test exits 1 with no test result: line.
  • In isolation with timeout 300: exit 124 — still hung after five minutes.

CI is unaffected — Test + coverage (>=80% lines, gated) is SUCCESS on the same commit, because the
runner is Linux. That asymmetry is what makes this worth fixing rather than tolerating: the gate is
green while the local suite is unrunnable, so the failure only ever appears on a developer's machine.

Why this is worth a ticket rather than a shrug

A hang is indistinguishable from a dead lane. Three lanes on this repo died silently in one session
and the first hypothesis each time was a cap or a watchdog; a suite that hangs on the same machine
adds a second indistinguishable cause. It also means no Windows lane can use the local suite as
evidence — it must defer to CI for every claim.

Scope

The deliverable is a suite that terminates on Windows with a correct verdict. The likely fix is to
bound the server side — set_read_timeout on the accepted stream, and read to the end of the request
head rather than to EOF, since the assertion only inspects headers — and to dial 127.0.0.1 while
keeping the test's actual subject intact. The subject must be preserved: its whole point
(control.rs:1153-1158) is that an explicitly user-configured non-loopback node still receives the
token, which is the §5.3 direction where an over-strict fix would be a worse regression than the bug.
Its control test immediately above must keep failing if that guarantee is broken. Do not simply
#[cfg(not(windows))] it away — that would delete Windows coverage of a §5.3 guarantee.

Evidence required

A full cargo test -p dig-app-core --lib completing on Windows with a test result: line, plus the
mutation check that the preserved subject still fails when the token is withheld from a
UserConfigured endpoint.


Re-measured 2026-08-19 at sweep time — PARTIAL: the hang is MITIGATED, the ask is NOT satisfied

Carried into the body because a move to this repo does not bring comment history.

dig-app origin/main @ 8a146c1. The test is now at
crates/dig-app-core/src/control.rs:1184 and carries:

#[test]
#[cfg_attr(
    windows,
    ignore = "hangs on Windows: untimed accept()/read_to_end() on a 0.0.0.0 dial (dig_ecosystem#2705)"
)]
fn a_user_configured_host_off_loopback_still_receives_the_token() {

So the local suite now terminates on Windows — the acute symptom is gone. What has NOT been done
is the deliverable: the test body at :1185-1216 is unchanged, still
TcpListener::bind(("0.0.0.0", 0)), still an untimed accept(), still read_to_end() to EOF, still
dialling "0.0.0.0".

The mitigation's own doc comment (:1173-1178) states the remaining work verbatim:

"ignore rather than cfg(not(windows)) on purpose: the test still COMPILES on Windows, so it
cannot rot behind a cfg while the code it covers changes, and it can still be run there
deliberately with -- --ignored once the fix lands. … Un-skip by bounding THIS side —
set_read_timeout on the accepted stream, and read the request head rather than to EOF, since only
headers are asserted — and dialling 127.0.0.1."

A worse consequence than the ticket originally recorded also surfaced and is now fixed by the same
mitigation
(:1169-1171): the wedged harness "ran the Native confirmer (windows-latest) job into
GitHub's six-hour ceiling on every merge to main after it landed."
So this was not local-only.

What is left, and why it is not "just delete the ignore"

Windows coverage of a §5.3 guarantee is currently absent. The subject — an explicitly
user-configured non-loopback node still receives the control token — is the direction where an
over-strict fix is a worse regression than the bug. Its control test
(an_auto_discovered_host_off_loopback_is_refused_without_dialling, :1226) must keep failing if
that guarantee is broken.

Done condition: cargo test -p dig-app-core --lib completes on Windows with a test result: line
and this test runs there (no ignore), plus the mutation check that the preserved subject still
fails when the token is withheld from a UserConfigured endpoint.

Parent epic

https://github.com/DIG-Network/dig_ecosystem/issues/1533 (EPIC: drive the full dig-node/dig-app
system home).

kind:maintenance — a user cannot perceive the outcome. It is not blocking a named business
ticket today: the acute form (a six-hour CI job on every merge, and an unrunnable local suite) is
already mitigated, so what remains is restoring platform coverage rather than unblocking shipping.


Orchestrator ticket: https://github.com/DIG-Network/dig_ecosystem/issues/1533

Moved from DIG-Network/dig_ecosystem#2705 so this repo's own PR can close it with Closes (CLAUDE.md §1.3).
GitHub refuses a private-to-public issue transfer, so the body was copied and comment history
stayed on the original.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions