Skip to content

fix(worker): dial control.json's bindAddress instead of hardcoding loopback - #30

Open
Eyalm321 wants to merge 1 commit into
mainfrom
fix/worker-honours-bindaddress
Open

fix(worker): dial control.json's bindAddress instead of hardcoding loopback#30
Eyalm321 wants to merge 1 commit into
mainfrom
fix/worker-honours-bindaddress

Conversation

@Eyalm321

Copy link
Copy Markdown
Owner

The bug

worker.rs built its control-API base URL as http://127.0.0.1:{port}, ignoring control.json's
bindAddress:

let base = format!("http://127.0.0.1:{}", disco.port);

When the app binds a specific address (the mobile-client remote-access path) that is a
single-socket bind, so loopback is not listening. Every POST /queues/<q>/claim fails with
ConnectionRefused and the worker exits before claiming anything.

Why it is worth fixing rather than working around

The failure is near-silent from the outside. Worker panes sit running/idle and tasks stay
queued with claimedBy: null — which reads as an empty queue or a botched fan-out, not a config
fault. The only tell is a worker pane's tail:

[g182-w1] online — draining 'g182'
Error: reqwest::Error { kind: Request, url: "http://127.0.0.1:41419/queues/g182/claim",
  source: ConnectError("tcp connect error", 127.0.0.1:41419, Os { code: 111, ConnectionRefused }) }

Observed against a tailnet bind, where it made every queue fan-out in the goals system drain
nothing. It was diagnosed only after mistaking it for bad task payloads.

The fix

control_cli::base_url already encodes exactly the right rule, so it is reused rather than
duplicated: dial a specific address directly (bracketing IPv6), keep loopback for an unspecified
(0.0.0.0/::) or absent bind. bindAddress is #[serde(default)], so legacy control.json
files are byte-for-byte unaffected
.

Tests

Three tests in worker::tests, driven from real control.json payloads:

  • a specific IPv4 bind → http://100.120.216.17:41419
  • a specific IPv6 bind → http://[fd7a::1]:41419 (bracketed)
  • absent and 0.0.0.0 and ::http://127.0.0.1:41419

The expression lives in a control_base() helper so the tests call the same path run() does.
Verified by mutation: reverting control_base to the hardcoded loopback turns the two
specific-address tests red (17 passed / 2 failed), and restoring it returns 19/19. A test that
rebuilt the expression itself would have passed either way — that earlier draft is what prompted
the refactor.

Full app-crate suite green: 160 + 2 passed, 0 failed. worker.rs is rustfmt-clean (several other
files in the crate have pre-existing fmt diffs; untouched).

Not covered here

The npm MCP bridge (hyperpanes-mcp, dist/control/discovery.js) has the identical defect — it
also builds http://127.0.0.1:<port> and drops bindAddress, so mcp__hyperpanes__* tools fail
the same way. That lives outside this repo.

…opback

`worker.rs` built its base URL as `http://127.0.0.1:{port}`, ignoring
`control.json`'s `bindAddress`. When the app binds a specific address (the
mobile-client remote-access path) that is a single-socket bind, so loopback is
not listening: every `POST /queues/<q>/claim` fails with ConnectionRefused and
the worker exits before claiming any task.

The failure is near-silent from the outside. Worker panes sit `running`/`idle`
and tasks stay `queued` with `claimedBy: null`, which reads as an empty queue or
a bad fan-out rather than a config fault — you only see it by reading a worker
pane's tail. Observed against a tailnet bind, where it made every queue fan-out
in the goals system drain nothing.

`control_cli::base_url` already encodes the right rule and is reused here rather
than duplicated: dial a specific address directly (bracketing IPv6), keep
loopback for an unspecified (`0.0.0.0`/`::`) or absent bind, so legacy
control.json files are byte-for-byte unaffected.

The expression lives in `control_base()` so the tests exercise the same path
`run()` does — reverting it to a hardcoded loopback turns them red, which a test
that rebuilt the expression itself would not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant