Add aspire do --watch for attaching to pipeline execution progress
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
## Summary
Add a read-only way for Aspire CLI and external tools to attach to an already-running deployment/pipeline execution and observe progress, using the auxiliary AppHost backchannel.
The target CLI experience is:
```bash
aspire do --watch
aspire do --watch --format json
```
`--watch` must not start a new pipeline execution. It should attach to the current pipeline execution for the selected running AppHost.
## Motivation
Today, tools can show live deployment progress only if they launch `aspire do ` or `aspire deploy` themselves and parse the owning CLI process output. A viewer should be able to attach after a deployment has already started, similar to how existing commands attach to a running AppHost for resources/logs.
Existing attach-style commands already use the auxiliary backchannel and AppHost discovery machinery. Deployment/pipeline execution currently uses a private CLI-owned publish backchannel, so it does not provide multi-client discovery, replay, or current execution state for late observers.
## Proposed command behavior
```bash
aspire do --watch
```
Attach to the active pipeline execution for the selected AppHost. If there is no active execution, report that none is running.
For v1, assume a single active pipeline execution per AppHost. Running parallel deployments for the same project/AppHost is not a primary scenario, and `--watch` should not require users to understand or pass an execution ID.
If the AppHost ever reports multiple active executions, the CLI should treat that as an ambiguity/error in v1 rather than designing the main UX around that case.
```bash
aspire do --watch --format json
```
Emit a snapshot-first NDJSON event stream suitable for tools and live pipeline viewers.
Useful optional flags:
```bash
--apphost # select the running AppHost, matching existing attach commands
--since # resume from a known event sequence
--replay all|none|tail # default: all available replay
--tail # when replay=tail
```
## Required data model
Add an AppHost-side pipeline execution registry exposed over the auxiliary backchannel behind a new capability, for example `pipeline-observe.v1`.
Each AppHost should expose its current active pipeline execution, if any. Internally, the AppHost may still use a stable execution identifier/correlation ID for event envelopes, tests, logging, and future-proofing, but the v1 CLI should not require or center the UX around passing that ID.
Each execution should have:
- Internal correlation/execution ID
- Operation (`publish`, `deploy`, `destroy`, `do`)
- Target step, if any
- Started/completed timestamps
- Current status
- Step graph snapshot
- Current step/task status maps
- Redacted input/resource metadata
- Bounded replay buffer with monotonic sequence numbers
- Multi-observer fan-out for live events
Attach clients are observers only. They must not answer prompts, own process lifetime, or keep the AppHost alive after the owning CLI exits.
## JSON/NDJSON stream shape
`aspire do --watch --format json` should emit one JSON object per line.
Every object should use a stable envelope:
```json
{
"schema": "aspire.pipeline.event.v1",
"sequence": 42,
"timestamp": "2026-05-10T06:02:14.392Z",
"execution": {
"operation": "deploy",
"targetStep": "deploy"
},
"event": {
"type": "step",
"id": "deploy-api",
"status": "running"
}
}
```
The first event from `watch` should be a full snapshot:
```json
{
"schema": "aspire.pipeline.event.v1",
"sequence": 128,
"timestamp": "2026-05-10T06:02:10.000Z",
"execution": {
"operation": "deploy",
"targetStep": "deploy",
"status": "running",
"startedAt": "2026-05-10T06:01:50.000Z",
"completedAt": null
},
"event": {
"type": "snapshot",
"replay": {
"firstSequence": 94,
"lastSequence": 128,
"isTruncated": false
},
"steps": [
{
"id": "build-api",
"name": "Build api",
"resourceName": "api",
"dependsOn": [],
"status": "succeeded",
"startedAt": "2026-05-10T06:01:51.000Z",
"completedAt": "2026-05-10T06:01:58.000Z",
"message": "Build completed"
}
],
"tasks": [],
"inputs": [],
"resources": []
}
}
```
Expected event types:
- `snapshot`
- `step`
- `task`
- `log`
- `prompt` — observe only; `observerCanRespond` should be false
- `complete`
- `replay-truncated`
Suggested normalized statuses:
- `pending`
- `running`
- `succeeded`
- `warning`
- `failed`
- `skipped`
- `cancelled`
- `unknown`
## Example human output
For an interactive terminal, `aspire do --watch` should render a human-readable live progress view similar to the owning deployment command, but clearly in observer mode:
```text
Watching active pipeline execution for AppHost: MyApp.AppHost
Operation: deploy
Target step: deploy
✓ Build api
✓ Build web
⠋ Deploy api
• Pushing container image
• Updating Azure Container App
○ Deploy web
○ Publish outputs
[12:02:16] [api] Pushed image myregistry.azurecr.io/api:latest
[12:02:19] [api] Updating revision...
```
When the pipeline completes:
```text
Watching active pipeline execution for AppHost: MyApp.AppHost
Operation: deploy
Target step: deploy
✓ Build api
✓ Build web
✓ Deploy api
✓ Deploy web
✓ Publish outputs
Deployment completed in 1m 15s.
```
If no pipeline is running:
```text
No active pipeline execution found for AppHost: MyApp.AppHost.
```
If multiple active executions are ever reported by the AppHost, v1 should fail clearly rather than requiring users to choose an execution ID:
```text
Multiple active pipeline executions were reported for AppHost: MyApp.AppHost. Watching multiple executions is not supported yet.
```
## Example JSON stream output
For tools, `aspire do --watch --format json` should emit snapshot-first NDJSON. The first line gives the viewer enough information to build the graph immediately:
```json
{"schema":"aspire.pipeline.event.v1","sequence":12,"timestamp":"2026-05-10T06:02:10.000Z","execution":{"operation":"deploy","targetStep":"deploy","status":"running","startedAt":"2026-05-10T06:01:50.000Z","completedAt":null},"event":{"type":"snapshot","replay":{"firstSequence":1,"lastSequence":12,"isTruncated":false},"steps":[{"id":"build-api","name":"Build api","resourceName":"api","dependsOn":[],"status":"succeeded","startedAt":"2026-05-10T06:01:51.000Z","completedAt":"2026-05-10T06:01:58.000Z","message":"Build completed"},{"id":"deploy-api","name":"Deploy api","resourceName":"api","dependsOn":["build-api"],"status":"running","startedAt":"2026-05-10T06:01:59.000Z","completedAt":null,"message":"Deploying api"}],"tasks":[{"id":"deploy-api.push-image","stepId":"deploy-api","name":"Push container image","status":"running","message":"Pushing image"}],"inputs":[{"name":"subscriptionId","kind":"parameter","required":true,"hasValue":true,"isSecret":false,"value":"00000000-0000-0000-0000-000000000000"},{"name":"databasePassword","kind":"parameter","required":true,"hasValue":true,"isSecret":true,"value":null}],"resources":[{"name":"api","type":"project.v0"}]}}
```
Subsequent lines are incremental updates:
```json
{"schema":"aspire.pipeline.event.v1","sequence":13,"timestamp":"2026-05-10T06:02:14.392Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"step","id":"deploy-api","name":"Deploy api","resourceName":"api","status":"running","message":"Deploying api"}}
{"schema":"aspire.pipeline.event.v1","sequence":14,"timestamp":"2026-05-10T06:02:15.102Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"task","id":"deploy-api.push-image","stepId":"deploy-api","name":"Push container image","status":"running","message":"Pushing image"}}
{"schema":"aspire.pipeline.event.v1","sequence":15,"timestamp":"2026-05-10T06:02:16.500Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"log","stepId":"deploy-api","taskId":"deploy-api.push-image","level":"information","message":"Pushed image myregistry.azurecr.io/api:latest","category":"Aspire.Hosting.Publishing"}}
{"schema":"aspire.pipeline.event.v1","sequence":16,"timestamp":"2026-05-10T06:03:05.000Z","execution":{"operation":"deploy","targetStep":"deploy","status":"succeeded","startedAt":"2026-05-10T06:01:50.000Z","completedAt":"2026-05-10T06:03:05.000Z"},"event":{"type":"complete","status":"succeeded","message":"Deployment completed","durationMs":75000}}
```
This shape lets a viewer build the graph from `snapshot.steps[].dependsOn`, update nodes from `step` events, update sub-operations from `task` events, attach log lines to the relevant step/task, and finish when it receives `complete`.
## Tools this enables
This feature is intended to make external deployment observers possible without requiring those tools to spawn and own `aspire do` or parse terminal output.
Examples:
- A dedicated live pipeline viewer, such as `tjwald/aspire-pipeline-viewer`, can attach to a deployment that is already running and render the step graph/statuses directly from structured events.
- IDE extensions can show deployment progress in a tree/graph view while the deployment continues in another terminal.
- Dashboard or local developer tools can display deployment status alongside resource/log views.
- CI or automation wrappers can consume the NDJSON stream and produce richer progress annotations without becoming the process that starts the deployment.
## Implementation notes
Likely files/classes involved:
- `src/Aspire.Hosting/Pipelines/PipelineActivityReporter.cs`
- `src/Aspire.Hosting/Publishing/PipelineExecutor.cs`
- `src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs`
- `src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs`
- `src/Aspire.Hosting/DistributedApplicationBuilder.cs`
- `src/Aspire.Hosting/Backchannel/BackchannelLoggerProvider.cs` as a replay/fan-out pattern to copy
- `src/Aspire.Cli/Backchannel/IAppHostAuxiliaryBackchannel.cs`
- `src/Aspire.Cli/Backchannel/AppHostAuxiliaryBackchannel.cs`
- `src/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cs`
- `src/Aspire.Cli/Commands/DoCommand.cs`
- `src/Aspire.Cli/Commands/PipelineCommandBase.cs`
Tests should cover:
- Execution registry snapshot/replay/fan-out
- Status normalization
- Replay truncation behavior
- Auxiliary RPC capability and watch behavior
- Serializer context coverage
- `aspire do --watch` attach behavior
- JSON/NDJSON output shape
- No-active-execution behavior
- Multiple-active-execution ambiguity/error behavior, if the server can report that state
## Compatibility and lifecycle constraints
- Follow the backchannel compatibility guidance in `docs/specs/cli-backchannel.md`.
- Use additive request/response DTOs and capability negotiation.
- Preserve old/new CLI-AppHost compatibility.
- Do not expose secret input values.
- Do not make attach clients owners of the deployment process.
- Do not replay prompts as actionable prompts.
- Bounded replay must report truncation so viewers can show partial history honestly.
Contributor guide
Assessment
This issue has not been assessed yet.