-
Notifications
You must be signed in to change notification settings - Fork 47
🤖 feat: stop dedicated Coder workspace on archive #2154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@codex review |
Add unit tests for WorkspaceLifecycleHooks ordering/error handling, verify WorkspaceService.archive persists archivedAt only after successful beforeArchive hooks, and cover createStopCoderOnArchiveHook stop behavior. Document the stop-on-archive default + config flag for Coder runtimes.
Fix ESLint issues by removing unnecessary escaping in a template literal and by avoiding async arrow functions that never await in tests.
Clarifies that archiving refers to Mux workspaces, while stopping refers to the underlying dedicated Coder workspace.
WorkspaceLifecycleHooks now supports afterUnarchive hooks and WorkspaceService.unarchive runs them after persisting unarchivedAt. Add CoderService.startWorkspace plus a Coder afterUnarchive hook that starts mux-created dedicated Coder workspaces (best-effort, gated by stopCoderWorkspaceOnArchive). Update unit tests and docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 26869fe236
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
26869fe to
7d9d236
Compare
|
@codex review Addressed the note about out-of-order writes on the Settings toggle by serializing/coalescing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7f12c59de2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Updated the toggle persistence logic to drain pending updates while a write is in-flight, ensuring the latest selection is persisted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9971ece632
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
Addressed the latest Codex note about interrupting active streams when archive later fails: archive() now runs beforeArchive hooks first, and only then best-effort interrupts the stream on success (with a regression test).\n\n@codex review |
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Adds backend workspace lifecycle hooks to manage Mux-created dedicated Coder workspaces when a Mux workspace is archived/unarchived (config-gated, default ON):
Also adds a Settings toggle, tests, and docs.
Background
Archiving a Mux workspace previously only updated
archivedAt(and interrupted streaming), which could leave the underlying Coder workspace running.Implementation
WorkspaceLifecycleHooks(beforeArchive,afterUnarchive).WorkspaceService.archive()runsbeforeArchivehooks synchronously and blocks archival if any hook fails.WorkspaceService.unarchive()runsafterUnarchivehooks best-effort after persistingunarchivedAt.existingWorkspace !== true.stopCoderWorkspaceOnArchive(default ON; persisted only when false) and a Settings toggle with clarified copy.Validation
make static-checkRisks
coder stop/start ... --yes; older CLIs without--yescould cause stop/start failures.📋 Implementation Plan
Plan: Workspace lifecycle hooks + stop Coder workspaces on archive
Context / Why
Archiving a workspace in Mux currently only updates metadata (
archivedAt) and (optionally) interrupts an active AI stream. For Coder-backed workspaces this means the underlying Coder VM can keep running (and costing money) after the workspace is hidden.Goal: add a general-purpose workspace lifecycle hook system so Mux can run runtime/service-specific actions on lifecycle events. Then use it for the Coder integration so that archiving a Mux workspace stops the underlying Coder workspace.
Product requirements (from user)
runtimeConfig.coder.existingWorkspace !== true).Evidence (repo facts)
src/node/services/workspaceService.ts→archive()setsarchivedAt, emits updated metadata; interrupts stream if streaming.RuntimeConfig.type === "ssh"withruntimeConfig.coderpresent:src/node/runtime/runtimeFactory.tscreatesCoderSSHRuntimewhen SSH config hascoder.src/node/runtime/CoderSSHRuntime.tsusescoderService.getWorkspaceStatus()andcoder ssh --wait=yesto auto-start; no stop call exists.src/node/services/coderService.tshasgetWorkspaceStatus(),createWorkspace(),deleteWorkspace().src/browser/components/ProjectSidebar.tsxtracksarchivingWorkspaceIds.src/browser/components/WorkspaceListItem.tsxrenders secondary-row textArchiving...whenisArchiving.Recommended approach (general-purpose hook registry)
Implement a small, typed backend hook registry (
WorkspaceLifecycleHooks) that supports blocking (pre-) lifecycle hooks.WorkspaceService.archive()will callbeforeArchivehooks before persistingarchivedAt.beforeArchivehook that:existingWorkspace,coder stop, with timeout,This keeps
WorkspaceServicefree of runtime-specific logic while still allowing runtime/service-specific behavior.Net new LoC estimate (product code)
~300–420 LoC
Implementation details (in order)
1) Add global setting:
stopCoderWorkspaceOnArchive(default ON)Why: users need a way to disable the new behavior (and revert to fast metadata-only archive).
Type:
src/common/types/project.tsProjectsConfig:Config parse/save:
src/node/config.tsloadConfigOrDefault()parsestopCoderWorkspaceOnArchive?: unknownusingparseOptionalBoolean.saveConfig()persists the field only when false (since default is true).ORPC schema + router:
src/common/orpc/schemas/api.tsunderexport const config:getConfig.output:updateMuxGatewayPrefs):src/node/orpc/router.ts→config.getConfighandler should return:stopCoderWorkspaceOnArchive: config.stopCoderWorkspaceOnArchive ?? trueconfig.updateCoderPrefshandler thateditConfig()sets:stopCoderWorkspaceOnArchive: input.stopCoderWorkspaceOnArchive ? undefined : falsefalseonly;trueuses default semantics)Notes on default-ON persistence
undefinedwhen enabled avoids rewriting~/.mux/config.jsonfor the default case.undefinedastrue.2) Add general-purpose lifecycle hook registry (backend)
File:
src/node/services/workspaceLifecycleHooks.ts(new)Proposed API shape (blocking pre-archive hook only for now, but structured to extend later):
Defensive behavior:
Err("...").WorkspaceLifecycleHooksshould still wrap each call intry/catchand convert throws toErrwith a safe message.3) Wire hooks into
WorkspaceService.archive()File:
src/node/services/workspaceService.tsAdd a private field + setter (matches existing pattern:
setTerminalService,setMCPServerManager, etc.):Update
archive(workspaceId)flow:editConfig():await this.getInfo(workspaceId).Err("Workspace not found").await this.workspaceLifecycleHooks?.runBeforeArchive({ workspaceId, metadata }).Err, surface that error to the UI and do not setarchivedAt.Pseudo-diff (high level):
4) Implement
coder stopsupportFile:
src/node/services/coderService.tsAdd a method using the existing
runCoderCommand()+interpretCoderResult()helpers (with timeout):Implementation notes:
getWorkspaceStatus()first and early-return if alreadystopped,deleted, ordeleting.not_foundas success (nothing to stop).5) Coder integration: register a
beforeArchivehook that stops dedicated workspacesWhere to implement the hook logic:
src/node/runtime/coderLifecycleHooks.ts(keeps Coder-specific code near the runtime without bloatingCoderSSHRuntime.ts).Hook factory:
Register it in
src/node/services/serviceContainer.ts:WorkspaceLifecycleHooks.shouldStopOnArchive: () => (this.config.loadConfigOrDefault().stopCoderWorkspaceOnArchive ?? true)this.workspaceService.setWorkspaceLifecycleHooks(hooks).6) Frontend setting toggle + “progress UI”
6a) Toggle in Settings (default ON)
File:
src/browser/components/Settings/sections/GeneralSection.tsx(or a new small section if preferred)api.config.getConfig()and storestopCoderWorkspaceOnArchivein component state.api.config.updateCoderPrefs({ stopCoderWorkspaceOnArchive: checked }).6b) Progress UI during archive
This is already mostly present:
ProjectSidebarsetsarchivingWorkspaceIdswhile awaitingonArchiveWorkspace().WorkspaceListItemrenders a secondary row with “Archiving…”.Minimal additional UX (optional but nice):
ProjectSidebar, changearchivingWorkspaceIdsfromSet<string>toMap<string, "archiving" | "stopping">(or store a label string).archivingLabelprop toWorkspaceListItem.7) Update Storybook mocks
File:
src/browser/stories/mocks/orpc.tsconfig.getConfig()response to includestopCoderWorkspaceOnArchive.config.updateCoderPrefs.8) Tests
Add focused unit tests to cover behavior without requiring the real Coder CLI:
WorkspaceLifecycleHooks
Err.Err.WorkspaceService.archive
beforeArchivehook returnsErr, archive returnsErrand does not persistarchivedAt.Ok, archive proceeds.Coder stop hook (unit-level)
existingWorkspace=true→ does not callcoderService.stopWorkspace.runtime.coder.workspaceName→ no-op.stopWorkspace.Suggested location:
src/node/services/workspaceService.test.tswith a newdescribe("archive lifecycle hooks").9) Docs
File:
docs/runtime/coder.mdxAlternative approach (not recommended): extend the Runtime interface
Instead of a registry, add
Runtime.onWorkspaceArchived?()and haveWorkspaceService.archive()instantiate a runtime and call it.Why not: for Coder it forces runtime instantiation + SSH transport wiring just to call
coder stop, and it pushes a UI concept (archive) into the low-level runtime interface.Net new LoC estimate (product code): ~220–350 LoC (similar overall, but less clean separation)
Generated with
mux• Model:openai:gpt-5.2• Thinking:xhigh• Cost:$13.02