Repository navigation
[Hackathon] Target URL preflight leaves DNS lookup outside its timeout and abort handling #502
Description
Verified against (commit SHA or released version)
988c035 (v0.14.0), Node 24.19.0, Windows 11
Discord Username / User ID
resquwue
What does this improvement do?
The pre-charge --target-url reachability check awaits DNS before creating its 8-second HTTP timeout or observing shutdownSignal. A stalled resolver therefore leaves probeTargetUrl pending beyond the stated preflight budget. Aborting the supplied signal while DNS is pending also does not settle the operation.
This can stall a run command before its trigger request on a machine with a slow/broken resolver. The HTTP timeout cannot help because fetch has not started. I reproduced both outcomes using the public DNS injection seam in the real built CLI module, with no external network or paid test runs.
Details / implementation notes
Offline reproduction
Build this commit with npm ci and npm run build. Save as repro-dns.mjs at the repository root and run node repro-dns.mjs:
import { probeTargetUrl } from './dist/lib/target-url-preflight.js';
import { InterruptError } from './dist/lib/errors.js';
const controller = new AbortController();
let settled = false, fetchCalls = 0;
const start = performance.now();
const probe = probeTargetUrl('https://example.test', {
dnsLookup: () => new Promise(() => {}),
fetchImpl: async () => { fetchCalls++; return new Response('ok'); },
shutdownSignal: controller.signal,
proxyActive: false,
}).then(() => { settled = true; }, () => { settled = true; });
controller.abort(new InterruptError('SIGINT'));
await new Promise(r => setTimeout(r, 100));
console.log('after abort', { settled, aborted: controller.signal.aborted, fetchCalls });
await Promise.race([probe, new Promise(r => setTimeout(r, 8100))]);
console.log('after budget', { settled, fetchCalls,
elapsedMs: Math.round(performance.now() - start) });Actual on my machine:
{"afterAbort":{"settled":false,"aborted":true,"fetchCalls":0},"afterPreflightBudget":{"elapsedMs":8219,"settled":false,"fetchCalls":0}}The unresolved fake DNS promise models a resolver that has not answered. It creates no sockets and does not keep the probe process alive. A bounded real-resolver delay has the same control-flow omission until the lookup settles. This repro demonstrates the preflight module contract; it does not claim the process signal handler can never force-exit the CLI.
Expected: DNS is included in a bounded preflight lifetime. A DNS timeout should follow the existing narrow refuse-versus-advisory policy; caller interruption should propagate promptly rather than wait for DNS or be converted into an advisory. No trigger should be dispatched after interruption.
Root cause and proposed scope
In src/lib/target-url-preflight.ts, probeTargetUrl awaits dnsLookup(host) at line 152. The shutdown signal, critical-operation tracking and AbortSignal.timeout(PREFLIGHT_TIMEOUT_MS) are only established for the later HTTP phase (lines 202 onward). The current timeout therefore bounds HTTP only, leaving DNS unbounded.
Propose a bounded, interruptible DNS phase using the existing shutdown/deadline conventions, with timer/listener cleanup and explicit handling of a late resolver result/rejection. Account for the fact that an underlying system lookup may not itself be cancellable. Preserve literal-IP DNS skipping, existing network classification and --skip-preflight behavior. No runtime dependency addition is proposed.
Regression cases: never-settling DNS hits the deadline; interrupt and an already-aborted signal settle promptly without fetch/trigger calls; late DNS resolution/rejection cannot start HTTP or cause an unhandled rejection; successful DNS and existing advisory/refusal cases remain unchanged. Use fake timers for fast deterministic tests, then lint/format/typecheck/full tests/coverage >=80%/build/local e2e for the eventual PR.
Duplicate check
Searched the current open/closed issue and PR registry through #501. #397 is classification of the environment API preflight timeout. #403/#406 concern an HTTP retry backoff losing its caller signal. #437 concerns run/plan polling transport retries. #390 concerns DNS64 address-family fallback. This report is specifically the DNS phase of the target URL reachability probe, before any HTTP request or retry has begun.
No production source changes have been made. Implementation will wait for maintainer triage and assignment.
Season 4 CLI Improvement Bonus
- Entering the Season 4 CLI Improvement Bonus; Discord: resquwue.
Confirmations
- I have searched existing issues and this is not a duplicate.
- I have provided my Discord identity above for reward coordination.
Activity
fyZhang66 commentedon Oct 20, 2026
Confirmed on v0.15.0, thanks @lxcario.
probeTargetUrlawaits the DNS lookup before creating the 8 s timeout and before observing the shutdown signal. Accepted, scope:No new dependency. Comment
/assignto claim it.lxcario commentedon Oct 20, 2026
/assign
testsprite-hob commentedon Oct 20, 2026
Assigned to @lxcario. Thanks for taking this on. (2 more slots available.)
Your open assigned issues (1/3):
(none)