microsoft/aspire · error · Error

HMP1 protocol error: invalid frame length

Error message

HMP1 protocol error: invalid frame length ${length} (type=${type}).

What it means

Aspire's hosting pipeline exposes a built-in Diagnostics step that dumps the dependency graph of pipeline steps for troubleshooting. The dump operates on the step data resolved (flattened/validated) during the last ExecuteAsync call, cached in _lastResolvedSteps. If no execution has happened yet, that cache is null and the library throws this InvalidOperationException instead of producing misleading empty output.

Solutions

  1. Execute the pipeline first by calling await pipeline.ExecuteAsync(model) before running the diagnostics step, so the resolved step data is cached.
  2. Verify the diagnostics call targets the same DistributedApplicationPipeline instance that executed; create a new pipeline instance loses _lastResolvedSteps.
  3. In tests or tooling, run ExecuteAsync inside try/catch or with a throwaway model to populate diagnostics data, then dump the graph.
  4. If diagnostics must run standalone, inspect the pipeline via public step enumeration APIs instead of the Diagnostics step.

Example fix

// before
await diagnosticsStep.ExecuteAsync(context); // throws: no execution yet
// after
await pipeline.ExecuteAsync(appModel);       // populates resolved step data
await diagnosticsStep.ExecuteAsync(context); // now dumps dependency graph
Defensive patterns

Strategy: validation

Validate before calling

// C#: ensure the pipeline executed before diagnostics
if (pipeline.GetType().GetProperty("HasSteps") is null)
    throw new InvalidOperationException("Pipeline not initialized");
await pipeline.ExecuteAsync(model); // must run before diagnostics step

Type guard

bool IsPipelineExecuted(CompilationResult? lastResult) => lastResult is not null;

Try / catch

try
{
    await pipeline.ExecuteAsync(model);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("No resolved pipeline data available"))
{
    logger.LogWarning(ex, "Diagnostics ran before pipeline execution; run ExecuteAsync first.");
}

Prevention

When it happens

Trigger: Running the pipeline's diagnostics step (WellKnownPipelineSteps.Diagnostics) before ever calling DistributedApplicationPipeline.ExecuteAsync, or after recreating the pipeline instance. The diagnostics step is invoked in a context (e.g. debugging hook, --diagnostics flag path) that does not itself execute the pipeline first.

Common situations: Developers wiring up pipeline diagnostics tooling or tests that invoke the diagnostics step directly; running diagnostics in a separate process or pipeline instance from the one that executed the model; calling diagnostic dumps from app-model inspection code before publish/deploy has run.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Dashboard/wwwroot/js/hmp1-client.js:80

      }
      yield frame;
    }
  }

  _tryReadOne() {
    if (this._totalLength < HEADER_SIZE) {
      return null;
    }
    const header = this._peek(HEADER_SIZE);
    const dv = new DataView(header.buffer, header.byteOffset, HEADER_SIZE);
    const type = dv.getUint8(0);
    const length = dv.getInt32(1, true);
    // Reject negative lengths (would desync the buffer because
    // `total < HEADER_SIZE`) and absurdly large lengths (would attempt to
    // allocate a multi-GiB Uint8Array which either throws inside the
    // `message` handler - silently wedging the client - or OOMs the tab).
    if (length < 0 || length > MAX_FRAME_PAYLOAD) {
      throw new Error(`HMP1 protocol error: invalid frame length ${length} (type=${type}).`);
    }
    const total = HEADER_SIZE + length;
    if (this._totalLength < total) {
      return null;
    }
    const payload = this._take(total).slice(HEADER_SIZE);
    return { type, payload };
  }

  _peek(n) {
    return this._concat(n, /* consume */ false);
  }

  _take(n) {
    return this._concat(n, /* consume */ true);
  }

  _concat(n, consume) {

View on GitHub (pinned to 25830f84bd)