Skip to content

fix: add timeout to registry URL validation to prevent CLI hang#2086

Open
phusi319 wants to merge 2 commits intoasyncapi:masterfrom
phusi319:fix/registry-timeout-2027
Open

fix: add timeout to registry URL validation to prevent CLI hang#2086
phusi319 wants to merge 2 commits intoasyncapi:masterfrom
phusi319:fix/registry-timeout-2027

Conversation

@phusi319
Copy link
Copy Markdown

Fixes #2027

Problem

When --registry-url points to an unreachable host (e.g., http://10.255.255.1), registryValidation() calls fetch() without a timeout. The CLI hangs indefinitely, stalling CI pipelines and leaving users without feedback.

Root Cause

ypescript // registry.ts (before) const response = await fetch(registryUrl as string); // no timeout, no AbortController

Fix

  • AbortController with 10s timeout the fetch aborts after 10 seconds if the host is unreachable
  • HEAD instead of GET lightweight validation only needs to check connectivity, not download the response body
  • Differentiated error messages:
    • Timeout Registry URL validation timed out after 10s: <url>
    • Network error Unable to reach registry URL: <url>
    • Auth (401) existing auth error preserved
  • Timer cleanup in finally block to prevent resource leaks

Changes

File Change
src/utils/generate/registry.ts Add AbortController, HEAD method, timeout handling
test/unit/utils/registry.test.ts New unit tests for URL parser and timeout behavior

Testing

  • registryURLParser: validates http/https, rejects invalid URLs
  • registryValidation: confirms unreachable URLs fail within 15s (10s timeout + margin) instead of hanging indefinitely

…capi#2027)

Add AbortController with 10s timeout to registryValidation() fetch call.
When --registry-url points to an unreachable host, the CLI now fails fast
with a clear error instead of hanging indefinitely.

Changes:
- Use HEAD method instead of GET for lightweight validation
- Add AbortController with 10s timeout
- Differentiate timeout vs network vs auth errors in messages
- Clear timer in finally block to prevent resource leaks
- Add unit tests for URL parser and timeout behavior
@phusi319 phusi319 requested a review from Souvikns as a code owner March 27, 2026 12:14
Copilot AI review requested due to automatic review settings March 27, 2026 12:14
@github-project-automation github-project-automation bot moved this to To Triage in CLI - Kanban Mar 27, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 27, 2026

🦋 Changeset detected

Latest commit: 6bba54c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@asyncapi/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our contributors guide useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out this issue.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a timeout to registry URL validation so the CLI fails fast instead of hanging indefinitely when a registry host is unreachable (Fixes #2027).

Changes:

  • Add AbortController + 10s timeout and switch validation request to HEAD in registryValidation().
  • Improve error messaging to distinguish timeout vs. general reachability failures.
  • Add unit tests for registryURLParser() and registryValidation() behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/utils/generate/registry.ts Adds timeout/abort handling to prevent CLI hangs during registry validation.
test/unit/utils/registry.test.ts Adds tests for URL validation and fast-failure behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +27 to +43
it('should fail fast for unreachable URLs instead of hanging', async () => {
const start = Date.now();
try {
// 10.255.255.1 is a non-routable IP that will trigger the timeout
await registryValidation('http://10.255.255.1');
expect.fail('Should have thrown');
} catch (error: unknown) {
const elapsed = Date.now() - start;
expect(elapsed).to.be.lessThan(15_000); // Must resolve within 15s (10s timeout + margin)
expect(error).to.be.instanceOf(Error);
const msg = (error as Error).message;
expect(msg).to.satisfy(
(m: string) => m.includes('timed out') || m.includes('Unable to reach'),
`Expected timeout or unreachable error, got: ${msg}`
);
}
}).timeout(20_000);
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unit test makes a real network call to http://10.255.255.1 and assumes it is unreachable. That IP is private (10/8) and can be routable in some CI/VPN/corporate environments, making the test flaky and potentially slow. Prefer stubbing global.fetch to a never-resolving promise that rejects on AbortSignal abort, so the timeout behavior is tested deterministically without external networking.

Copilot uses AI. Check for mistakes.
}
}).timeout(20_000);

it('should throw auth error for 401 without credentials', async () => {
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case is empty (no assertions and no skip), so it currently provides no coverage and can be misleading. Either implement it (e.g., stub fetch to return a Response with status 401) or mark it as skipped/todo so it doesn’t look like coverage exists when it doesn’t.

Suggested change
it('should throw auth error for 401 without credentials', async () => {
it.skip('should throw auth error for 401 without credentials', async () => {

Copilot uses AI. Check for mistakes.
Comment on lines 22 to +32
if (response.status === 401 && !registryAuth && !registryToken) {
throw new Error('You Need to pass either registryAuth in username:password encoded in Base64 or need to pass registryToken');
}
} catch {
throw new Error(`Can't fetch registryURL: ${registryUrl}`);
} catch (error: unknown) {
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Registry URL validation timed out after ${REGISTRY_TIMEOUT_MS / 1000}s: ${registryUrl}`);
}
if (error instanceof Error && error.message.includes('registryAuth')) {
throw error;
}
throw new Error(`Unable to reach registry URL: ${registryUrl}`);
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auth error preservation currently relies on checking whether error.message contains the substring registryAuth. This is brittle (message changes will silently alter behavior) and could misclassify unrelated errors. Prefer restructuring so only the fetch call is inside the try/catch (rethrow non-fetch errors), or use a dedicated error type/constant for the 401-without-credentials case and check against that instead of parsing the message.

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To Triage

Development

Successfully merging this pull request may close these issues.

[BUG] CLI hangs indefinitely when --registry-url points to an unreachable host (no timeout handling)

2 participants