Skip to content

fix(run): a hop route to a module that starts after the hop is frozen, not refused - #1178

Merged
antoinetoussaint-byte merged 1 commit into
mainfrom
fix/hop-routes-frozen-on-fresh-run
Oct 26, 2026
Merged

antoinetoussaint-byte merged 1 commit into
mainfrom
fix/hop-routes-frozen-on-fresh-run

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byte antoinetoussaint-byte commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What broke

With CLI 0.1.213 (and main), robin's playground — once it composes the document store's tool runnable-bindings and annotations 3fae5dc1 — died at authority/hop runtime-init on every fresh start:

Runner.Init: cannot get workspace dependencies configurations: published hop routes changed: stop and restart this Codefly run to adopt them; incremental refresh cannot replace its routing snapshot

Root cause

pkg/orchestration/hop_configurations.go freezeHopAddresses required every hop route's producer to already have recorded network mappings (s.networkMappings[unique]), and treated "no recorded mapping" as a changed route. A fresh run starts the hop before every routed module ordered after it, and a module that calls through the hop is (annotations calls robin through the hop, and the hop routes annotations). That module has loaded but not initialized, so the hop's resolution derives its address (producerNetworkMappings → localProducerMappings, the address its own Init will be proposed) — and the freeze refused it. Instrumented on the live run: route annotations/annotations/connect … selected=<derived localhost:52650> recorded=[].

It was not the excluded document-store services: the store's API catalog publishes only documents/documents, so no route names them. A routed service that never loaded still has no mapping and is still refused by name (hop route … has no resolved mapping).

Fix (at the owner)

  • A routed service that has not initialized is frozen at the derived address; one that has must still be at the address it recorded (unchanged).
  • RecordNetworkMappings now checks every frozen route owned by the recording service on the first record too (before: only on a reload). So the late module's Init is the check: the same address is accepted; an Init that moves or drops a routed address is refused naming the route, and nothing is recorded. This is strictly tighter than before — the mid-run refusal is not loosened.
  • Every restart refusal names the route and the reason.

Fix, not a hack: the freeze lives where the snapshot is taken and the check where addresses are accepted; no flag, no fallback.

Reused: the existing fence (routingMappings, RecordNetworkMappings, NetworkMappingHash) and the existing derivation of an uninitialized producer's address. Nothing new resolves addresses.

Tests

TestHopRoutesFreezeAModuleThatInitializesAfterTheHop: the hop's delivery succeeds with an uninitialized routed module and routes it at its proposed address; that module's Init is refused when it accepts another address or drops the endpoint, and accepted at the frozen one; the next delivery reads the same snapshot. Sabotage-checked both halves: without the freeze, the hop's delivery fails with the reported error; without the first-record check, the moved Init is accepted. go test ./pkg/orchestration/ ./cmd/run/, go vet, golangci-lint run ./pkg/orchestration/ are clean.

Live evidence

robin authority/interface 7389d1ac + the playground store-tools patch, own naming scope, fresh codefly run:

  • main 2b804f1: dies at authority/hop runtime-init with the error above.
  • this branch: authority/hop initializes, then annotations/annotations initializes after it at the frozen address; every service comes up and authority/edge runs (pre-rebase build of this commit).
  • a second fresh run on the pushed head afe6427 again passed the hop and annotations' Init; that run's edge then timed out on tracing/evidence, because its ClickHouse data directory (~/Library/Caches/codefly/clickhouse/c043abb67fcd3379) is the one another naming scope's live run already holds ("Another server instance in same directory is already running"). That collision is unrelated to this change and is reported separately.

🤖 Generated with Claude Code

…, not refused

A fresh run starts authority/hop before every routed module ordered after
it, and a module that calls through the hop is (robin's playground with
annotations 3fae5dc1). When the hop resolved its generated routes, that
module had loaded but not initialized, so freezeHopAddresses found no
recorded mapping for it and refused the hop's own start with "published
hop routes changed: stop and restart this Codefly run" on every fresh
start, although nothing had changed.

The address the hop's resolution derived for such a module is the one its
own Init will be proposed. The snapshot now freezes it, and the module's
Init is the check: RecordNetworkMappings refuses any accepted set that
drops or moves an address a hop snapshot holds, on the first record as
well as on a reload (before, only a reload was checked). A module that
never loaded still has no mapping and is refused by name; a module that
has initialized must still be at the address it recorded. The mid-run
refusal is unchanged, and every restart refusal now names the route and
the reason.

Tests: TestHopRoutesFreezeAModuleThatInitializesAfterTheHop — the hop's
delivery succeeds with an uninitialized routed module and routes it at
its proposed address; that module's Init is refused when it accepts
another address or drops the endpoint, and accepted at the frozen one.
Sabotage-checked both halves: without the freeze the hop's delivery fails
with the reported error; without the first-record check the moved Init
is accepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 8ad1797 Oct 26, 2026
15 checks passed
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