microsoft/aspire · error · AspireError

$error

Error message

$error

What it means

invoke_capability() raises AspireError(result['$error']) when the AppHost returns a structured error response for the capability call. The literal '$error' in the message is the response key inspected here; the raised AspireError carries the server-side error code and message. Type-mismatch errors are converted to a formatted type error instead via _format_type_error.

Solutions

  1. Read the raised AspireError's code/message to identify the server-side cause.
  2. Verify the capability_id matches one advertised by the AppHost (list capabilities or check generated stubs).
  3. Fix argument types to match the capability schema; use the generated wrapper methods where available.
  4. Regenerate the Python module if the AppHost version changed and capabilities were renamed.

Example fix

// before
result = client.invoke_capability("add_resoruce", {"name": 123})
// after
result = client.invoke_capability("add_resource", {"name": "cache"})
Defensive patterns

Strategy: try-catch

Validate before calling

def assert_capability(client, capability_id, capabilities):
    if capability_id not in capabilities:
        raise ValueError(f'unknown capability {capability_id!r}')

Try / catch

try:
    result = client.invoke_capability(capability_id, args)
except AspireError as e:
    log.error('capability %s failed: %s (%s)', capability_id, e.message, getattr(e, 'code', None))
    raise

Prevention

When it happens

Trigger: Calling client.invoke_capability(capability_id, args) where the server rejects the call: unknown capability id, invalid/mistyped arguments, or any capability-level failure surfaced through the $error field.

Common situations: Typos in capability_id; passing Python values the server cannot unmarshal (wrong types, unsupported fields); calling a capability removed or renamed after an AppHost/SDK update; server-side validation rejecting the argument dict.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/d80b1462822f75f8. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.CodeGeneration.Python/PythonModuleBuilder.cs:1150

                self,
                capability_id: str,
                args: dict[str, typing.Any] | None = None,
                kwargs: typing.Mapping[str, typing.Any] | None = None
            ) -> typing.Any:
                '''
                Invoke an ATS capability by ID.

                Capabilities are operations exposed by [AspireExport] attributes.
                Results are automatically wrapped in Handle objects when applicable.
                '''
                self._check_connection()
                result = self._send_request("invokeCapability", capability_id, self._marshal_transport_value(args or {}))

                # Check for structured error response
                if _is_ats_error(result):
                    if result["$error"].get("code") == AtsErrorCodes.TYPE_MISMATCH:
                        raise _format_type_error(result["$error"])
                    raise AspireError(result["$error"])

                # Wrap handles automatically
                return _wrap_if_handle(result, self, kwargs)

            def _marshal_transport_value(self, value: typing.Any) -> typing.Any:
                if callable(value):
                    return self.register_callback(value)
                if isinstance(value, dict):
                    return {key: self._marshal_transport_value(nested_value) for key, nested_value in value.items()}
                if isinstance(value, (list, tuple)):
                    return [self._marshal_transport_value(item) for item in value]
                return value

            def _send_request(self, method: str, *params: typing.Any) -> typing.Any:
                '''Send a JSON-RPC request and wait for response'''
                with self._lock:
                    self._request_id += 1
                    request_id = self._request_id

View on GitHub (pinned to 25830f84bd)