Repository navigation
test(e2e): attribute native-config-networks probe timeouts to a URL, phase and route state - #470
Merged
Merged
Conversation
…phase and route state The mixed routed ingress check failed once in hosted CI (HACK-1419) with `hack exec` exit 27: the in-container peer probe's 4 s fetch timeout. The log could not say which service pair, which URL (origin or alias) or which phase (name resolution or HTTP) stalled, and no captures leave the runner. The peer probe now resolves the name first and fetches the resolved address, under one shared per-URL budget (unchanged 4 s), and exits with distinct dnsRejected/dnsTimeout codes for the resolution phase. Before a failing exit it writes one closed stderr line: URL label, phase, resolution and total milliseconds, and the resolved address count. On a failed peer probe the scenario logs a value-free probe diagnostic: role, service and target, the closed evidence, the source container's getent answer reduced to an address count and whether every address is the target on a shared bridge, both containers' states and health, attachment counts and ingress attachment, and the outcome of one diagnostic-only rerun. The step still fails; the rerun only classifies transient versus persistent. Refs HACK-1419 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roodboi
marked this pull request as ready for review
October 24, 2026 14:38
roodboi
added a commit
that referenced
this pull request
Oct 24, 2026
…K-1428) (#484) Follow-ups after #470 (e08f971). Test and fixture-harness changes only; no product code changes. ## Root cause 1. **Shared-budget subtraction untested.** Local DNS resolution of `127.0.0.1` or `localhost` takes about 0 ms, and the hung-probe bound (< 2000 ms for a 300 ms budget) was loose. So `Math.max(1,${timeout}-d)` → `${timeout}` still passed. 2. **Resolved-address fetch untested.** Every probe test used names that the system resolver also resolves to the listener. A fetch to the original hostname reached the same server, so removing `target.hostname = …x.address` still passed. 3. **Create shim oracle flake (`Expected: 71, Received: 128`).** The test makes nine shim invocations in sequence, and each one starts one or two Bun processes. On a loaded host this took longer than bun test's 5 s default timeout. When the timeout fires, bun test SIGTERMs the still-running shim ("killed 1 dangling process"). The runner mapped the signaled exit (`code === null`) to a bare `128`. The timed-out test then keeps running while later tests run, and its `expect(…).toBe(71)` fails. Bun reports that as "Unhandled error between tests", so the visible failure is `Expected 71 / Received 128` and not the timeout. A minimal repro outside the repo showed the same thing: a test whose child outlives the timeout gets `exit code=null signal=SIGTERM`, then `Expected: 71 Received: 128` as an unhandled error between tests. The same mechanism also hit `probe scripts exit with a closed code…` (`Received: 143`, from `Bun.spawn`'s 128+15). ## Fix - `tests/native-networks-fixture.test.ts` - New `peer probe fetches the address it just resolved`: replaces `Bun.dns.lookup` in the probe child with a stub that resolves RFC 6761 `.invalid` names to 127.0.0.1. The probe must pass, and the server must see `Host: 127.0.0.1:<port>` on both requests. A second, real resolution of a `.invalid` name cannot succeed. - New `peer probe charges resolution time against the shared per-URL budget`: the stub delays resolution by 1200 ms with a 2000 ms budget against a hung endpoint. It asserts `dnsMs ≥ 1150`, `elapsedMs ≥ 1950`, and `elapsedMs < 2600` (a fresh full budget ends near 3200). It also checks that a 500 ms budget with a 1200 ms resolution exits `dnsTimeout` (31) with `phase: dns` at 450–1200 ms. - New `capture reports a signaled command as 128 plus its signal number` (SIGTERM → 143, SIGKILL → 137). - `setDefaultTimeout(60_000)` for this file, following `tests/native-compose- ownership.test.ts`. Every fixture command and probe keeps its own, tighter timeout. - `tests/e2e/native-config-networks-acceptance.ts`: `runNativeNetworkFixtureCommand` reports a signaled command as `128 + signal number`, as a shell does, so a future kill names its signal. The timeout and oversize paths still throw before any exit code is returned. Nothing compared the exit code with `128`. ## Evidence (local, macOS, Bun 1.4.2; host load average 45–110 from other work) Mutations (each reverted afterwards): | Mutation | Before this PR (`-t probe`) | With this PR | |---|---|---| | `Math.max(1,${timeout}-d)` → `${timeout}` | 5/5 pass | budget test fails: `Expected: < 2600, Received: 3211` | | drop only `target.hostname=…x.address;` (keep `let ok=false;`) | 5/5 pass | address test fails (`code 26`, not 0); budget test fails (`Expected 27, Received 26`) | | runner `code ?? 128` (signal ignored) | n/a | signal test fails: `Expected: 143, Received: 128` | Flake loops (`bun test tests/native-networks-fixture.test.ts`, private `HOME`/`HACK_HOME`/`DOCKER_CONFIG`, `HACK_SECRETS_DISABLE_KEYCHAIN_FALLBACK=1`, no Docker): | Run | Before fix | After fix | |---|---|---| | shim test alone (`-t "strict argv oracle"`), 40 runs | 21 pass / 19 fail, all at the 5 s timeout | 39 pass / 1 fail (the failure was not this test; see gaps) | | whole file, 10 / 30 runs | 8 / 10: both failures were the shim test timing out with `Expected: 98, Received: 128` plus the closed-code probe test with `Received: 143` | 30 / 30 pass | Gates: `bun run check` (includes `privacy:check`) passes, `bun run --cwd packages/cli typecheck` reports 0 errors (it includes `tests/`), commitlint passes, and gitleaks 8.30.1 finds no leaks over `origin/next..HEAD`. The CLI surface and agent guidance did not change. Hosted CI at the PR head is the full-suite evidence. ## Gaps - Hosted `test` and docker-e2e shards at this head are the full-suite proof; Docker e2e was not run locally. - In 1 of the 40 post-fix runs of the single test, the shim test itself passed, but the checkout write guard's `afterAll` `git status` timed out at 5 s under load (`tests/helpers/checkout- write-guard.ts`). That is outside this unit and is reported separately. - The IPv6 branch of the address rewrite (`[${x.address}]`) still has no test. - Stopping a loop mid-run (killing its `bun test`) left one Bun `SIGABRT` crash report from an orphaned child. The loops that ran to completion added none. Refs HACK-1428 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs HACK-1419
Summary
This PR does not fix a root cause. The hosted evidence cannot tell a readiness race from a real routing defect, so it is not safe to change product code or relax the assertion. What it does is make the next failure conclusive: the log will name the URL, the phase, and the route state.
What failed
On #463, run 40057262783, job 120762873007 (
docker-e2e shard 1/4),native-config-networksfailed at the mixed routed ingress check:NATIVE_NETWORK_PROBE_EXIT.timeout: the in-container peer probe (bun -e, run throughhack exec <service>) hit its 4 sAbortSignal.timeoutwhile fetchinghttp://<target>:3000orhttp://<target>-alias:3000/.markers(primary)right after the routedhack up. In that phasewebis attached to the externalhack-devingress as well as its authoredoutboundbridge.observe(primary)runs just beforemarkers, and it requires every container to behealthy. The healthcheck is an in-container fetch of127.0.0.1:3000, so every server was already listening. A server that was not listening yet would refuse the connection and exit 26 at once, not stall until timeout.Frequency
I checked the docker-e2e shard jobs of the last 60
ci.ymlruns onnextand PR branches: 228 jobs, 215 green, 10 in progress, 3 failed. The 3 failures:native-config-networksfailure, sibling-bhack execwithE_COMPOSE_FAILEDstorage-helper-start/probe-timeout. That is the storage helper budget, not ingress.native-compose-adoption-job-worktrees, which is unrelated.So the exit 27 has appeared once in about 51 shard-1 runs.
Candidate causes (none confirmed)
web. Here the name answers an address that is not on a bridge the two containers share.Each of these produces exit 27 today, and they cannot be told apart from the log.
Change (test fixture only)
nativeNetworkFixturePeerProberesolves first, then fetches. It resolves the name (Bun.dns.lookup, IPv4 preferred) and then fetches the resolved address. Resolution and the HTTP exchange share the same 4 s per-URL budget (NATIVE_NETWORK_PEER_PROBE_BUDGET_MS), so the assertion is not loosened. Resolution failures get their own exit codes,dnsRejected30 anddnsTimeout31. Codes 26 and 27 now mean the HTTP phase only.probe-evidence <origin|alias> <dns|http> <dnsMs> <elapsedMs> <addressCount>. It never includes an address or body.Network probe diagnostic {...}. The line contains:getent ahostsv4 <probed name>returned inside the source container, plus whether every one is the target's address on a shared bridge;Network probe diagnostic unavailable.How to read the next occurrence
phase:"dns"with a passing rerun: a resolver stall. Consider a resolver-aware budget.resolvesToSharedTarget:false: a real routing or DNS defect. Fix it in the product, with a unit test.phase:"http", a shared target address, and a failing rerun: a persistent connectivity defect.Validation
bun test tests/native-networks-fixture.test.tsgives 63 pass. There are 4 new tests: probe phase and URL attribution, the evidence parser, route-state reduction, and the closed probe diagnostic. The existing probe exit-code test still passes.everychanged tosome, evidence regex unanchored, rerun dropped, ingress ignored.bun run check(includes privacy:check) passes.bun run --cwd packages/cli typecheckandbun run typecheckreport 0 errors. Biome is clean on both changed files. gitleaks 8.30.1 finds no leaks overorigin/next..HEAD.docker-e2eshard 1 runsnative-config-networksand exercises the new probe in real containers.Gaps
Hosted CI at 3e88132
docker-e2eshards pass, as does thedocker-e2eaggregate. In shard 1 (run 40060640525, job 120773164774),PASS native-config-networks (2288.2s), including the mixed routed ingress phase, with the new probe running in real containers.test,secret-scan,linux-process-lifetime,runtime-images, Runtime state models, and Config compiler (ubuntu, arm, macos-latest) pass. Candidate core and Config compiler on macOS were still running when this was written.bun installtook 47 s against 36 s. I attribute the difference to the runner, not the probe, but one run cannot rule out a small per-probe cost.🤖 Generated with Claude Code