-
Notifications
You must be signed in to change notification settings - Fork 855
Polyglot TypeScript AppHost: confusing error when missing 'await' on async builder calls #14724
Description
Description
When writing a TypeScript AppHost, if the developer forgets an await on an async builder call (e.g., addPostgres), the error surfaced during aspire run is a confusing internal .NET error rather than a helpful message pointing to the TypeScript coding mistake.
Steps to Reproduce
- Create a TypeScript AppHost with the following
apphost.ts:
const postgres = builder.addPostgres("postgres");
const counterdb = postgres.addDatabase("counterdb");
const frontend = builder.addNodeApp("frontend", "./frontend", "server.js")
.withNpm()
.withHttpEndpoint({ port: 3000, env: "PORT" })
.withExternalHttpEndpoints()
.withReference(counterdb)
.waitFor(counterdb);- Run
aspire run
Actual Behavior
The following error is displayed:
❌ Capability Error: The service collection cannot be modified because it is read-only. Code: INTERNAL_ERROR Capability: Aspire.Hosting.PostgreSQL/addDatabase ❌ An unexpected error occurred: The TypeScript (Node.js) apphost failed. 📄 See logs at C:\Users\joperezr\.aspire\logs\cli_20260226T053154_dfc30a18.log
This error is confusing because:
- It references an internal .NET concept ("service collection is read-only") that has no meaning to a TypeScript developer
- The error code
INTERNAL_ERRORgives no actionable guidance - There is no indication that the root cause is a missing
awaitin the user's TypeScript code
Expected Behavior
The error should be more actionable. Ideally:
- Detect that the TypeScript process exited with an error and surface it as a compilation/runtime error in the guest AppHost
- Provide guidance like: "Your apphost.ts encountered an error. Check that async calls like
addPostgres()andaddDatabase()are properly awaited." - Or at minimum, surface the underlying TypeScript/Node.js error output clearly rather than the .NET-side capability error
Root Cause Analysis
The issue is that addPostgres() returns a PostgresResourceBuilderPromise (thenable). Calling .addDatabase() on it chains correctly via the Promise thenable pattern, but the result (counterdb) is still a Promise that hasn't been resolved yet. When this unresolved promise is passed to .withReference(counterdb), it gets sent over JSON-RPC as an invalid argument, causing the .NET server to fail with an internal error.
The correct code requires await:
const postgres = await builder.addPostgres("postgres");
const counterdb = await postgres.addDatabase("counterdb");Suggestion
Consider one or more of these improvements:
- Client-side validation: The generated SDK could validate that arguments passed to capability methods are resolved handles, not pending promises, and throw a clear TypeScript error like "Cannot pass an unresolved promise as an argument. Did you forget 'await'?"
- Server-side error mapping: When the server receives an invalid argument type, map it to a user-friendly error rather than exposing the internal .NET exception
- Error output forwarding: When the guest AppHost process fails, capture and display its stderr/stdout prominently instead of (or in addition to) the RPC error