Skip to content

Fix Invalid URL in Loader.getConsumedModules for prefix-form module IDs#4243

Merged
habdelra merged 3 commits intomainfrom
cs-10498-fix-loader-invalid-url
Mar 24, 2026
Merged

Fix Invalid URL in Loader.getConsumedModules for prefix-form module IDs#4243
habdelra merged 3 commits intomainfrom
cs-10498-fix-loader-invalid-url

Conversation

@habdelra
Copy link
Contributor

Summary

  • Same root cause as Fix TypeError: Invalid URL when serving cards from prefix-mapped realms #4241 but in a different location: Loader.getConsumedModules in loader.ts
  • After the import-maps change, module identifiers can be in registered prefix form (e.g. @cardstack/catalog/...). getConsumedModules passed these directly to new URL() which throws TypeError: Invalid URL
  • The fix uses resolveCardReference() to resolve the prefix to a real URL first
  • This fixes the client-side error seen when prerendering catalog cards:
    Failed to render live search result: TypeError: Failed to construct 'URL': Invalid URL
        at Loader.getConsumedModules (loader.ts:169:36)
        at m.loadScopedCssForInstance (live-prerendered-search.ts:160:48)
    

Test plan

  • Added regression test in loader-test.ts that calls getConsumedModules with a prefix-form identifier — without the fix new URL('@test-loader/f') throws TypeError: Invalid URL, with the fix it resolves correctly

Fixes CS-10498

🤖 Generated with Claude Code

habdelra and others added 2 commits March 24, 2026 16:44
Same root cause as the relativizeResource fix: after the import maps
change, module identifiers can be in registered prefix form (e.g.
@cardstack/catalog/...). Loader.getConsumedModules passed these directly
to new URL() which throws TypeError: Invalid URL. Use
resolveCardReference() to resolve the prefix to a real URL first.

This fixes the client-side error seen when prerendering catalog cards:
"Failed to render live search result: TypeError: Failed to construct
'URL': Invalid URL at Loader.getConsumedModules"

Fixes CS-10498

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dules

Verifies that getConsumedModules works when called with a registered
prefix-form module identifier (e.g. @cardstack/catalog/...). Without
the fix, new URL('@test-loader/f') throws TypeError: Invalid URL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions
Copy link

Preview deployments

@backspace
Copy link
Contributor

Do you think this would supersede #4242?

Copy link
Contributor

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

This PR fixes a client-side TypeError: Invalid URL that occurs when Loader.getConsumedModules is called with a prefix-form module identifier (e.g. @cardstack/catalog/...) by resolving the prefix to a real URL before constructing a URL.

Changes:

  • Update Loader.getConsumedModules to resolve prefix-form module identifiers via resolveCardReference() before calling new URL().
  • Add a regression test ensuring getConsumedModules('@test-loader/f') works once a prefix mapping is registered.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
packages/runtime-common/loader.ts Resolves prefix-form module IDs before URL parsing inside getConsumedModules.
packages/host/tests/unit/loader-test.ts Adds regression coverage for getConsumedModules with a registered prefix-form module identifier.
Comments suppressed due to low confidence (1)

packages/runtime-common/loader.ts:180

  • After attempting await this.import(...) inside getConsumedModules, the local module variable is not refreshed from this.modules. When module was initially missing (or in fetching), this can cause the method to return [] even though the import succeeded, because module?.state is still based on the pre-import value. Re-read the module from the cache (using the resolved href) after the import attempt before checking its state and walking consumedModules.

    if (!module || module.state === 'fetching') {
      // we haven't yet tried importing the module or we are still in the process of importing the module
      try {
        await this.import<Record<string, any>>(moduleIdentifier);

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

Comment on lines +170 to +174
}

let resolvedModuleIdentifier = new URL(moduleIdentifier);
let resolvedModuleIdentifier = new URL(
resolveCardReference(moduleIdentifier, undefined),
);
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

getConsumedModules tracks visited modules and self-exclusion using the raw moduleIdentifier string, but the module lookup now normalizes via resolveCardReference(). If the initial identifier is a registered prefix (e.g. @cardstack/...) and a dependency cycle leads back to the same module via its resolved URL, moduleIdentifier !== initialIdentifier will be true and the function can incorrectly include the starting module in the returned consumed list (and also miss de-duping between prefix vs URL forms). Consider normalizing both moduleIdentifier and initialIdentifier to the same resolved/canonical href before doing consumed.includes(...) and the self-exclusion check, and store only that normalized form in consumed.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Fixed in 099cf4d — both moduleIdentifier and initialIdentifier are now normalized to their resolved URL href before cycle detection and self-exclusion, so prefix-form and URL-form of the same module are treated consistently.

@habdelra
Copy link
Contributor Author

Do you think this would supersede #4242?

this work is very specifically around the Loader.getConsumedModules() method. I see that #4242 does not take that into consideration. likely you'll need both.

…le detection

Resolve both moduleIdentifier and initialIdentifier to their canonical
URL href before cycle detection and self-exclusion checks. This ensures
prefix-form identifiers (e.g. @cardstack/catalog/...) and their resolved
URL equivalents are treated as the same module, preventing incorrect
self-inclusion or missed de-duplication in dependency cycles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Mar 24, 2026

Host Test Results

    1 files  ±    0      1 suites  ±0   4h 12m 20s ⏱️ + 2h 3m 57s
2 031 tests +    1  2 015 ✅ ±    0  15 💤 ± 0  0 ❌ ±0  1 🔥 +1 
4 092 runs  +2 047  4 060 ✅ +2 030  30 💤 +15  1 ❌ +1  1 🔥 +1 

For more details on these errors, see this check.

Results for commit 099cf4d. ± Comparison against base commit 7ce5f7b.

♻️ This comment has been updated with latest results.

Copy link
Contributor

@backspace backspace left a comment

Choose a reason for hiding this comment

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

The host failures look unrelated to me

@habdelra habdelra merged commit e5831b4 into main Mar 24, 2026
135 of 157 checks passed
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.

3 participants