Skip to content

Stream workspace action progress over WebSockets - #366

Open
fioan89 wants to merge 10 commits into
mainfrom
websocket-workspace-progress
Open

fioan89 wants to merge 10 commits into
mainfrom
websocket-workspace-progress

Conversation

@fioan89

@fioan89 fioan89 commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Workspace action progress was previously refreshed only by the five-second polling loop, which made stop, update-and-start, and update-and-restart operations appear stale between polls.

We added a workspace progress watcher that uses Coder's workspace and build-log WebSockets on supported deployments. The remote environment uses those events only for the progress description, while the workspace poller remains responsible for workspace and agent state.

We added version gating and failure handling so older deployments and unavailable WebSocket connections continue through the existing polling path. We also added coverage for streaming output, state isolation, lifecycle cleanup, and fallback behavior.

Workspace action progress was previously refreshed only by the five-second polling loop, which made start, stop, update-and-start, and update-and-restart operations appear stale between polls.

We added a workspace progress watcher that uses Coder's workspace and build-log WebSockets on supported deployments. The remote environment uses those events only for the progress description, while the workspace poller remains responsible for workspace and agent state.

We added version gating and failure handling so older deployments and unavailable WebSocket connections continue through the existing polling path. We also added coverage for streaming output, state isolation, lifecycle cleanup, and fallback behavior.
Workspace action progress was previously refreshed only every five seconds, and the streaming capability and transport responsibilities were spread across the environment, watcher, and REST client.

We added workspace progress WebSockets to the CLI feature set and changed the remote environment to choose between streaming and REST polling before invoking either path. The Coder API facade now defines the streaming endpoints, while the REST client owns WebSocket transport and event decoding.

We changed the workspace progress watcher to consume ID-based streaming methods and remain focused on build progression. Runtime failures still fall back to polling, and the updated coverage verifies feature selection, streaming behavior, state isolation, and failure handling.
Workspace action progress was previously available only every five seconds, and the first streaming implementation added unnecessary abstractions to an environment class whose mixed ordering made the behavior difficult to follow.

We removed the watcher interface, shortened the concrete watcher name, and changed the callback holder into three named function parameters. The remote environment now constructs the watcher directly, while the watcher keeps workspace-first argument ordering and remains focused on streamed build progress.

We reorganized CoderRemoteEnvironment in depth-first call order, grouped overridden entry points with their supporting methods, and moved file-level extensions below the class. The polling fallback and workspace and agent state ownership remain unchanged.
@fioan89
fioan89 marked this pull request as ready for review September 24, 2026 19:49
@fioan89
fioan89 requested review from code-asher, jeremyruppel and matifali and removed request for code-asher September 24, 2026 19:49
Comment thread src/main/kotlin/com/coder/toolbox/sdk/WorkspaceProgressWatcher.kt Outdated
if (log.id <= lastLogID) {
false
} else {
lastLogID = log.id

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm not sure how to better express this in kotlin, but because the lock routine returns a boolean, the lastLogID assignment here feels like a side effect. is there a good/clean way to tease these apart?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm not happy how the progress watcher turned out, I'll try to simplify and improve it.

Comment thread src/main/kotlin/com/coder/toolbox/sdk/WorkspaceProgressWatcher.kt Outdated
Comment thread src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt Outdated
Comment thread src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt Outdated
Workspace action progress was previously available only every five seconds, and the initial streaming path contained repeated action workflows and lifecycle details that were difficult to interpret.

We changed CoderRemoteEnvironment to share the workspace build request workflow and replaced the ambiguous retry flag with an explicit watcher restart path. The environment still uses polling for workspace and agent state and falls back to REST progress requests when streaming becomes unavailable.

We changed WorkspaceProgressWatcher to use its activity property consistently, isolated duplicate-log tracking, and distinguished expected build-log completion from abnormal socket closure. We also added coverage showing that abnormal closure disables streaming and activates failure handling.
Workspace build progress was provided only every five seconds by polling, leaving actions without timely feedback. We changed CoderRemoteEnvironment to share the action lifecycle and show queued progress immediately, while its polling loop remains responsible for workspace and agent state.

We changed WorkspaceProgressWatcher to stream build progress when supported, reconnect failed sockets with backoff, and retrieve logs through REST while streaming is unavailable. We changed CoderRestClient to report socket openings and removed CLI text-progress callbacks so the watcher owns progress descriptions. We added tests for action lifecycle, streaming, fallback, and reconnection.
@linear-code

linear-code Bot commented Sep 28, 2026

Copy link
Copy Markdown

JET-1

Workspace progress could be overwritten by delayed polling responses or
callbacks from replaced builds and connections, and watcher notifications
could race with shutdown.

We changed the workspace progress watcher to track each build and
connection separately and serialize state changes with notifications.
The watcher now rejects obsolete events and polling results before they
can affect the current build, while REST requests remain outside the
lock so network waits do not delay cleanup.

We changed the connection handler to resume log streaming as soon as a
connection opens and simplified its capped retry backoff. We added
watcher regression coverage for build replacement, delayed responses,
reconnects, shutdown, and failures during connection setup.
Workspace progress mixed build lifecycle, socket recovery, and polling
state, making cleanup difficult to follow. Synchronous log requests
could delay the provider's workspace refresh, while log socket failures
could leave progress stalled.

We changed the workspace progress watcher to process events in one
coroutine and documented its states and transitions. Each build session
now owns its log stream and pending request so replacement cancels old
work and excludes late results. We retained a callback lock so shutdown
waits for active notifications, and scope cancellation ends the watch.

We changed the watcher's socket flows to recover independently and
restore REST fallback whenever the log stream becomes unavailable.
Retries now reset after receiving data, and log tracking accepts an
initial zero ID while rejecting older output.

We changed the remote environment to deliver snapshots without waiting
for log retrieval, while the watcher retains only the latest queued
poll. We shared active build statuses between the environment and
watcher and added regression coverage for build transitions, reconnects,
cancellation, shutdown, and workspace refreshes during slow requests.
Silent WebSocket disconnects could leave workspace progress frozen, and
exceptions from progress callbacks could escape into the host-owned
coroutine scope.

We added 30-second heartbeat pings to the HTTP client so unresponsive
connections trigger the workspace progress watcher's existing recovery
and polling fallback. We changed the watcher to report progress callback
errors and close only the affected watcher, while preserving coroutine
cancellation and cleanup when failure reporting also throws.

We clarified shutdown ownership in the watcher and made its socket
callback names consistent. We added regression coverage showing that
callback failures release sockets and pending requests without
cancelling the parent scope or interrupting sibling watchers.
Workspace Start actions showed only generic queued progress while the CLI prepared a build, leaving authentication waits and other preparation output invisible.

We restored live stdout and stderr reporting in CoderCLIManager while retaining complete command output and exit-code failures. We changed CoderRemoteEnvironment to send each Start action's CLI output to its own WorkspaceProgressWatcher, and we added preparation events to the watcher so CLI messages appeared until build progress took over. The watcher ignored subsequent CLI output to keep stale messages from replacing build progress or appearing after shutdown.

We added coverage for live output from both streams, failures before build creation, and the transition from CLI preparation to build progress. All 80 focused tests passed, and static checks completed with warnings.
CoderCLIManagerTest produced an extra trailing space in stderr messages on Windows because cmd.exe included the space before the redirection operator in the echoed output. This caused two progress tests to fail their exact output assertions.

We changed the test scripts to place stderr redirection before each echo command so the messages remained consistent across Windows and POSIX shells. We retained the existing assertions and production behavior. Both affected tests passed locally on macOS; Windows validation remained pending.
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.

2 participants