Skip to content

backlog: file #1103, the connscale API port range is unverified past its base - #290

Merged
wshallwshall merged 1 commit into
mainfrom
claude/1103-connscale-api-port-range
Aug 8, 2026
Merged

backlog: file #1103, the connscale API port range is unverified past its base#290
wshallwshall merged 1 commit into
mainfrom
claude/1103-connscale-api-port-range

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Files BACKLOG #1103. Ledger-only change — one item appended to docs/BACKLOG.md, no code.

Observed, not hypothesised

PR #289's test (windows-2022, py3.14) leg, run 31261658519:

FAILED tests/test_connscale_smoke.py::test_connscale_smoke_end_to_end
  ConnScaleError: engine exited during startup:
  ERROR uvicorn.error: [Errno 13] error while attempting to bind on address
  ('127.0.0.1', 62748): [winerror 10013]
1 failed, 10770 passed, 830 skipped in 1608.73s (0:26:48)

The mechanism

  • _free_port() binds ("127.0.0.1", 0), reads getsockname()[1], and closes the socket in a finally before returning. The port is free at the instant it is read and reserved by nothing after.
  • The test calls it once for the API base.
  • harness/load/connscale/runner.py:162 then binds api_port + step for every sweep arm. The failing run's log carries 62746, 62747, 62748.

Exactly one of the three was verified.

Why the symptom hides the cause

A Windows bind onto a port held by another socket reports 10013access forbidden — not the 10048 that reads as a collision. Taken at face value it invites running CI elevated, adjusting firewall rules, or blaming the runner image, none of which touch a port-allocation defect.

The port number is the tell: 62748 is ephemeral, far outside the test's own inbound window [20000, 30000) — so it is not the family #1014 reserved.

This is #1014's defect, one port-family over

#1014 gave the inbound block a contiguous reservation, a random anchor to de-correlate concurrent worktrees, contiguity asserted at the acquisition site, and a loud failure rather than a silent fixed fallback. None of that reached the API family.

The test's own comment reasons about the API ports only against the inbound block:

"The sink/API ports stay ephemeral (above the inbound window) and won't hit the block."

That is true, and silent about the increment range colliding with anything else on the machine. A correct statement about one hazard reading as coverage of a hazard it never mentions is the #1000 shape, in prose.

The sink family carries the identical pattern at runner.py:278 (sink_port + i for i in range(sink_ports)) and is dormant only because the smoke test passes sink_ports=1. Any profile raising it inherits this item without touching it.

Three distinct failure modes now share this leg

mode item
empty_claims_monotonic throughput SLO #1101
windows-2025 step cap exceeded #1096 (fixed, 620281af)
API port bind at startup #1103, this item

The step here ran 27:00 against a 55:00 cap, which is what rules out the cap; and the failure is a bind before the engine finishes starting, which is what rules out the SLO. ci.yml's scope note already warns against merging distinct failures on this leg into one cause.

Not to be fixed with a retry

#1014 removed @pytest.mark.flaky(reruns=2) precisely so "a genuine future collision now surfaces as a RED rather than a masked retry". This is that collision, in the family #1014 did not cover — the marker working as intended, not a regression. Re-adding a retry would re-hide the class it was removed to expose.

Verification

Number allocated atomically via scripts/coord/alloc.ps1, never grepped; the ledger gate passed on commit. Banner invariant holds (exactly one status banner). tests/test_backlog_status_check.py: 19 passed. The repo-wide link gate: 5,359 links / 347 files, all resolve.

Census note: filed without a row in the re-scoring table, matching #1096 through #1101. The four census lines are recomputed from that table and are unchanged, so they remain accurate about it.

…its base

Observed failing on CI, not hypothesised. PR #289's windows-2022 leg died with

  [Errno 13] bind on ('127.0.0.1', 62748): [winerror 10013]

after 10,770 tests passed, with the step at 27:00 against a 55:00 cap. Not a
timeout, and not the throughput SLO already filed as #1101.

tests/test_connscale_smoke.py probes ONE free API port via _free_port(), which
binds port 0, reads getsockname(), and closes the socket before returning.
harness/load/connscale/runner.py:162 then binds api_port + step for every sweep
arm. The failing run used 62746, 62747 and 62748; exactly one of the three was
ever checked.

The symptom hides the cause. A Windows bind onto a held port reports 10013, not
the 10048 that reads as a collision, so it presents as an access-permissions
problem and invites fixes that touch nothing relevant. The port number is the
tell: 62748 is ephemeral, far outside the test's own inbound window
[20000, 30000), so it is not the family #1014 reserved.

This is #1014's defect one port-family over. #1014 gave the INBOUND block a
contiguous reservation, a random anchor, a contiguity assertion at the
acquisition site, and a loud failure instead of a silent fallback. None of that
reached the API family. The test's comment reasons about the API ports only
against the inbound block -- "The sink/API ports stay ephemeral (above the
inbound window) and won't hit the block" -- which is true, and silent about the
increment range colliding with anything else on the machine.

The sink family carries the identical pattern at runner.py:278 and is dormant
only because the smoke test passes sink_ports=1. Any profile raising it inherits
this item. The fix should cover both families.

#1014 removing the flaky(reruns=2) marker is why this is visible at all, and that
was correct: its stated intent was that a genuine future collision surface as a
RED rather than a masked retry. This is that collision, in the family it did not
cover. The fix must not be a re-added retry.

Records the mechanism, why the symptom misleads, the dormant sink instance, the
shape a guard must take (assert the whole reserved range, not the base), and the
separation from #1096 and #1101 with the measured step time.

Number allocated via scripts/coord/alloc.ps1, never grepped. Verified: banner
invariant holds, backlog hygiene 19 passed, the repo-wide link gate reports
5,359 links across 347 files all resolving.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 8, 2026 16:32
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Docs-only PR, so this is the repo's blind mode, not its cheap one: the drift guards live in pytest gated on code == 'true' and would skip pre-merge, firing on the push to main afterwards. Run locally instead, using the ungated doc-guard step's exact command line plus the now-live link gate:

186 passed, 89 skipped

All 89 skips are tests/test_threat_model_doc_drift.py against the vault-only docs/security/THREAT-MODEL.md — the ADR 0156 class ci.yml already documents. No skip is masking a failure in a guard that can see this tree.

@wshallwshall
wshallwshall merged commit 5e8404a into main Aug 8, 2026
33 checks passed
@wshallwshall
wshallwshall deleted the claude/1103-connscale-api-port-range branch August 8, 2026 16:34
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