Teardown's port-release wait probes all five fixed Harper ports unconditionally; an unrelated resident service on any one of them (here: MQTT on 1883) makes the wait unwinnable and every suite retires its address
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- node.js, typescript
- Domain
- testing
Research direction
Start at dist/harperLifecycle.js:29 and 426-435, then inspect dist/portUtils.js:36 and the startHarper/teardownHarper flow. Verify the wait is limited to ports requested by the instance, identify failing ports in the warning, and remove the escaped-child attribution unless a tree PID is confirmed as the holder; affected integration suites should no longer retire an address because of an unrelated resident service.
Written by the indexing model from the issue text.
Description
teardownHarper waits on ALL_HARPER_PORTS = [9925, 9926, 9927, 1883, 8883]
(dist/harperLifecycle.js:29) on the instance's loopback address, regardless of which ports
this Harper instance actually bound. waitForPortsFree (dist/portUtils.js:36) polls a
plain bind on each port and returns only when every one succeeds. A resident service that
permanently holds one of those ports on that address makes the wait structurally unwinnable:
it is not slow, it can never return true, at any timeout.
Measured this session: this development host runs a resident MQTT broker (yeti, pid 63153)
listening on 127.0.0.1:1883 (lsof, verified live). On stock macOS the practical pool is
127.0.0.1 (127.0.0.2+ unbindable without aliases), so every harness instance runs on the
same address the broker occupies. Every single teardown then logged:
Harper ports on 127.0.0.1 still in use after teardown (5000ms); NOT recycling the address
(a Harper child outlived the kill). The slot will be reclaimed when this process exits.
including a run with the timeout raised to 60000ms; the suites themselves were green (13-15s
each). The parenthetical is a misdiagnosis on this host: no Harper child outlived anything,
and nothing in the message names which port failed, so the operator is pointed at Harper's
process tree when the cause is an unrelated broker that predates the run. Whether the
instance under test even had an MQTT listener is not established here; the probe does not
care, which is the defect. The downstream consequence (the retired address deadlocks the
rest of a 1-address-pool run) is filed separately.
Suggested fix: scope the wait to ports Harper's own children bound.
- Cheapest: derive the port list from the config the harness itself passed to
startHarper
(it constructs--HTTP_PORT/--OPERATIONSAPI_NETWORK_PORT/--MQTT_NETWORK_PORT/... at
dist/harperLifecycle.js:426-435, so it already knows which listeners were requested). - Stricter: snapshot which of the fixed ports the instance's process tree actually held at
kill time (the harness already enumerates the tree to signal it) and wait only on those. - Either way, print the ports still failing the probe in the warning, and drop the
"a Harper child outlived the kill" attribution unless a tree PID is confirmed as the
holder.
The check's stated purpose (catch an escaped Harper child before recycling the address under
SO_REUSEPORT) survives both variants; a port this instance never bound cannot be held by its
escaped child.
Reproduction
- Any host where a resident service holds one fixed Harper port on the pool address. Stock
macOS with a local MQTT broker on 127.0.0.1:1883 reproduces it as found; otherwise
nc -l 127.0.0.1 1883 &before the run with
HARPER_INTEGRATION_TEST_LOOPBACK_POOL_START=1. - Run any suite through
startHarper/teardownHarper. - Observed: the suite passes; teardown always waits the full
PORT_RELEASE_TIMEOUT_MS
(5s default; 60s when overridden) and then logs the warning above; the address is retired
every time.lsof -nP -iTCP:1883 -sTCP:LISTENshows the holder is the resident broker,
not a process from Harper's tree.
Measured on
| Component | Version |
|---|---|
| @harperfast/integration-testing | 0.7.1 (dist/harperLifecycle.js:29,618-629, dist/portUtils.js:36) |
| harper (system under test) | 5.2.1 |
| Node | v24.16.0 |
| OS | macOS 26.5.2 (arm64, Darwin 25.5.0), stock loopback |
| resident holder | yeti pid 63153, LISTEN on 127.0.0.1:1883 (lsof, 2026-08-14) |
| suites | datadog-agent-binary bbeb99a integration tests |
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 0
- Avg merge
- 17d 7h
- Merged PRs (30d)
- 2
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from HarperFast/integration-testing
-
setupHarperWithFixture overwrites ctx.harper, dropping pre-set hostname (breaks multi-node add_node) Open
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
HarperFast/integration-testing#29 · 1 assignee ·
-
Difficulty 4/5 3-5 days Newbie friendliness 55/100
-
enhancement
Difficulty 5/5 Over a week Newbie friendliness 35/100
All issues in HarperFast/integration-testing
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
ontola/atomic-server#1625 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
melgarafael/DeskcommCRM#1451 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 82/100
-
bug via-triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bot:ai-assisted component:compact-js status:untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
midnightntwrk/midnight-sdk#403 ·