Skip to content

fix(mux): persist install under $HOME and reuse it when the version matches - #1095

Open
ibetitsmike wants to merge 3 commits into
mainfrom
mike/mux-persistent-install-cache
Open

fix(mux): persist install under $HOME and reuse it when the version matches#1095
ibetitsmike wants to merge 3 commits into
mainfrom
mike/mux-persistent-install-cache

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Every workspace start reinstalled Mux from npm into /tmp/mux, because /tmp is wiped between starts and the rendered start script always took the reinstall branch (use_cached defaults to false, so [ "false" != true ] forced bun add mux@next even when a copy was present). On the dogfood template this cost about 50 s per start and left a 502 window right after a restart (workspace build 663: container up 18:06:01Z, agent ready 18:06:53Z, with bun add alone reporting 17 s).

This changes the coder/mux module so the install is persistent and version-aware, and moves its lifecycle onto coder-utils:

  • install_prefix defaults to $HOME/.coder-modules/coder/mux and log_path to $HOME/.coder-modules/coder/mux/logs/mux.log (the per-module root from AGENTS.md) instead of /tmp.
  • On start, the install script reads the installed mux package version and resolves install_version through the selected package manager (npm view / pnpm view / bun pm view ... --registry <registry_url>), so private registry credentials from npm/pnpm/bun config apply. bun pm view only exists since Bun 1.2.15 (older releases print their usage to stdout and exit 1), so on older bun the lookup falls back to npm view when npm is present and then to the plain registry lookup. When the versions match it skips the install; when they differ, cannot be resolved, or nothing is installed, it installs as before. The tarball fallback (no package manager) keeps its unauthenticated curl lookup, matching how it downloads the tarball.
  • Only one install layout is kept (npm/ or .mux-package/), so the installed version is always read from the package behind the active mux symlink.
  • An empty install_version is normalized to latest once in Terraform and feeds the lookup, the package-manager install and the tarball path alike.
  • run.sh is split into scripts/install.sh.tftpl and scripts/start.sh.tftpl, rendered at plan time and orchestrated by coder-utils (module_directory = "$HOME/.coder-modules/coder/<slug>", so several instances on one agent do not share scripts, sync names or logs). The module now exposes the scripts output. This raises the provider constraint to >= 2.13 and, like the other coder-utils modules, needs a coder CLI with coder exp sync in the workspace.
  • Compatibility: with the default install_prefix, a copy pre-installed at the pre-1.6.0 default /tmp/mux is linked into the new prefix when install = false or use_cached = true, so existing templates keep starting. install = false (offline) otherwise behaves as before. Version bumped 1.5.0 to 1.6.0.

Verification

Rendered the install script with Terraform and ran it in a dogfood workspace (package_manager = "bun", install_version = "next", empty bun cache for the cold run):

Run Bun Outcome Elapsed
1, cold prefix, empty bun cache 1.2.15 bun add installs 653 packages 5.96 s
2, same prefix 1.2.15 mux@0.28.4-next.3.g3c06630a7 is already installed ...; skipping install (via bun pm view) 1.22 s
3, same prefix 1.2.15 same skip 1.08 s
4, same prefix, npm on PATH 1.2.14 same skip (via npm view fallback) 0.93 s
5, same prefix, no npm on PATH 1.2.14 same skip (via registry lookup fallback) 0.17 s

For comparison, the previous head (935ff33) on bun 1.2.14 printed does not match mux@next (Learn more about these at https://bun.sh/docs/cli/pm); reinstalling and reinstalled on every run.

The warm path is dominated by the registry lookup (about 1 s for bun pm view or npm view). Also checked: an older pinned install_version followed by a next run prints does not match mux@next (...); reinstalling and updates; an unreachable registry reports (unresolved) and falls through to the install path, which fails the same way it does today; the start script launches the binary from the prefix and logs into logs/mux.log.

  • terraform test: 26 passed (new: default prefix persists, custom prefix, legacy /tmp/mux fallback rendered only for the default prefix, empty version means latest, default log path, coder-utils run order). Runs asserting on the token-bearing start script use command = apply.
  • bun test main.test.ts: 10 passed. Both install tests (node:20-alpine via npm, alpine/curl via tarball) run the install script twice and assert the second run skips; a new test runs a pre-1.6.0 copy from /tmp/mux with install = false; launcher tests read the default log path and logs/install.log. A new test on oven/bun:1.2.14-alpine (forced package_manager = "bun") installs once and asserts the next two starts skip, first without npm and then with npm added; it fails against 935ff33.
  • prettier, terraform fmt, README validation clean; shellcheck on the rendered scripts reports only SC2050 (constant expressions inherent to rendered templates).

Xum acted on behalf of @ibetitsmike for this PR.

Generated with Xum using Claude.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-03T22:31:58.005721Z 0e85698 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

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: c2a495e74a

ℹ️ 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".

Comment thread registry/coder/modules/mux/run.sh Outdated
Comment thread registry/coder/modules/mux/main.tf
Comment thread registry/coder/modules/mux/scripts/install.sh.tftpl
Comment thread registry/coder/modules/mux/README.md
Comment thread registry/coder/modules/mux/scripts/install.sh.tftpl
Comment thread registry/coder/modules/mux/run.sh Outdated
Comment thread registry/coder/modules/mux/mux.tftest.hcl Outdated
…versions through the package manager

Round 2 of review on #1095:
- resolve install_version through npm/pnpm/bun view so registry auth applies
- default log_path into the module root logs/ directory
- split run.sh into scripts/install.sh.tftpl and scripts/start.sh.tftpl run by coder-utils
- keep a pre-1.6.0 copy at /tmp/mux working for install=false and use_cached
- keep a single install layout so the installed version is read from the active one
- normalize an empty install_version to latest before resolving it
- apply before asserting on the token-bearing start script

Copy link
Copy Markdown
Contributor Author

@codex review

Xum acted on behalf of @ibetitsmike: round 2 pushed as 935ff33 addressing all seven findings; re-requesting review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

node_dir="$HOME/.local/share/coder-mux/node-v$node_version-linux-$node_arch"

P1 Badge Move the bootstrapped Node runtime into the module root

On any workspace without node, this lifecycle still downloads persistent, module-managed runtime data into the ad-hoc $HOME/.local/share/coder-mux directory. Because this PR reworks the existing module's lifecycle and otherwise establishes the module root, leaving the runtime here keeps troubleshooting and cleanup split across locations; store it beneath $HOME/.coder-modules/coder/mux and update the start-side lookup accordingly.

AGENTS.md reference: AGENTS.md:L44-L47

ℹ️ 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".

Comment thread registry/coder/modules/mux/scripts/install.sh.tftpl Outdated
Comment thread registry/coder/modules/mux/main.tf
Comment thread registry/coder/modules/mux/main.tf
Comment thread registry/coder/modules/mux/scripts/install.sh.tftpl
Comment thread registry/coder/modules/mux/scripts/install.sh.tftpl
bun pm view only exists since Bun 1.2.15. Older releases print their usage
to stdout and exit 1, so the warm-start check compared the installed version
against 'Learn more about these at https://bun.sh/docs/cli/pm' and
reinstalled on every start. Check the exit status and fall back to npm view
(shares .npmrc credentials) and then to the plain registry lookup.

Adds a container test on oven/bun:1.2.14-alpine covering the reuse with and
without npm present.

ibetitsmike commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Follow-ups from the second review round are filed as #1096 (base64-encode additional_arguments), #1097 (slug-derived default paths), #1098 (tarball path removes npm/ before the download is verified), #1099 (forced package-manager check vs use_cached) and #1100 (bootstrapped Node.js runtime under the module root, from the review body). Round 4 added #1101 (Bun-only registry credentials are not carried by the Bun < 1.2.15 lookup fallback). This PR stays limited to the persistent, version-aware install; 0e85698 fixes the Bun < 1.2.15 version lookup.

Xum acted on behalf of @ibetitsmike for this comment.

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

node_dir="$HOME/.local/share/coder-mux/node-v$node_version-linux-$node_arch"

P1 Badge Keep the bootstrapped runtime inside the module root

When the workspace image lacks Node.js, this stores a module-managed persistent runtime under ~/.local/share/coder-mux, leaving runtime data outside the module's new $HOME/.coder-modules/coder/mux root. Because the module chooses and manages this location, it is not tool-defined state; place it beneath the module root and have both lifecycle scripts resolve the same path.

AGENTS.md reference: AGENTS.md:L43-L47

ℹ️ 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".

Comment thread registry/coder/modules/mux/scripts/install.sh.tftpl
@35C4n0r

35C4n0r commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

reviewing this.

DevelopmentCats pushed a commit that referenced this pull request Sep 4, 2026
## Description

The module installs `mux@<install_version>`, but the npm `mux` package
is only a compatibility shim (`packages/mux-compat` in coder/xum). Its
single dependency is an exact-pinned `@coder/xum` of the same version
string, and the two packages are published independently. `mux` usually
lands on the registry several minutes before its own `@coder/xum`
dependency, so any workspace that starts inside that window resolves
`mux@next` and then fails with:

```text
No version matching "<ver>" found for specifier "@coder/xum"
❌ Failed to install mux via bun
```

The `coder_script` exits 1, the workspace reports a start error, and the
Mux app goes unhealthy.

This PR installs `@coder/xum` directly. It is the package that actually
contains Mux, so there is no cross-package version coupling left to race
against: whatever version or dist-tag resolves is fully installable on
its own. `@coder/xum` ships both the `mux` and `xum` bins
(`node_modules/.bin/mux -> ../@coder/xum/dist/cli/index.js`), so the
existing binary probe, the `$INSTALL_PREFIX/mux` symlink, and everything
that calls `mux server` are unchanged. No CI change on the publishing
side is needed.

Changes in `registry/coder/modules/mux`:

- `run.sh`: `PKG="@coder/xum"` for the npm/pnpm/bun path; the
no-package-manager tarball fallback now fetches metadata from
`<registry>/@coder%2Fxum/<version>` (scoped names are URL-encoded in
registry metadata paths) and constructs
`<registry>/@coder/xum/-/xum-<version>.tgz`, which matches the
registry's `dist.tarball` exactly. Error and progress messages name the
package that is being installed.
- `main.tf`: `install_version` and `registry_url` descriptions name
`@coder/xum`. Default `install_prefix` moves from `/tmp/mux` to
`$HOME/.coder-modules/coder/mux` and default `log_path` from
`/tmp/mux.log` to `$HOME/.coder-modules/coder/mux/logs/mux.log`, the
per-module root from AGENTS.md (Module Data Layout), so restarts that
clear `/tmp` no longer discard the install or the diagnostics. `run.sh`
already creates both directories with `mkdir -p` before writing; no
other path in it assumed `/tmp`. The Node.js runtime that `run.sh`
bootstraps on images without `node` also moves from
`~/.local/share/coder-mux` to the same root, so all module data shares
one directory.
- `README.md`: documents the package switch and the new default paths;
fixes the "Pin Version" example, which claimed the default
`install_version` is `latest` while `main.tf` defaults to `next`, and
pins a version that exists on `@coder/xum`; links the renamed upstream
repository (coder/mux is now coder/xum).
- `mux.tftest.hcl`: new `installs_coder_xum_package` run asserting the
rendered script installs `@coder/xum` and uses the scoped
metadata/tarball URLs (verified red/green by flipping `PKG` back to
`mux`); new `default_paths_under_module_root` and
`custom_install_prefix_and_log_path` runs assert the default and
overridden paths (red/green by reverting the default to `/tmp/mux`); the
trailing-slash registry assertion follows the new metadata URL.
- `main.test.ts`: expected lines and the fake-`mux` fixtures follow the
new default paths (`/root/.coder-modules/coder/mux` inside the test
containers). The two install tests now remove their containers inside
the test instead of leaving them to the global `afterAll` hook in
`setup.ts`: that hook has a 5 second timeout, and deleting a container
holding a full `@coder/xum` install (about 800 MB, 48k files) took
longer than that on the CI runner under bun 1.4.1, failing the run after
every test had passed.

### Breaking change

`@coder/xum` only exists on npm from `0.28.2-next.24` onward (stable:
`0.28.3`, `0.28.4`); older releases were published under `mux` only. The
default (`next`), `latest`, and any pin of `0.28.3` or newer are
unaffected. Templates that pin `install_version` to an older version
will fail to install and need to move to `0.28.3` or newer. Private
mirrors configured via `registry_url` must serve the scoped `@coder/xum`
package. The default `install_prefix` and `log_path` also move out of
`/tmp` (see above), and a bootstrapped Node.js runtime is downloaded
once more into the new location: templates that relied on the old
defaults, for example `install = false` with a binary pre-installed at
`/tmp/mux/mux` or tooling that reads `/tmp/mux.log`, must set
`install_prefix` and `log_path` explicitly or move to the new locations.
Because existing valid configurations can stop working, this is released
as a major bump.

Note: this overlaps with #1095, which also reworks the install path of
this module. Whichever merges second needs a rebase; the package switch
is a small, self-contained change to port.

## Type of Change

- [ ] New module
- [ ] New template
- [x] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

**Path:** `registry/coder/modules/mux`
**New version:** `v2.0.0` (from `v1.5.0`, bumped with
`.github/scripts/version-bump.sh major`)
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally

- `terraform test -verbose` in the module: 24 passed, 0 failed.
- `bun test main.test.ts` in the module (Docker, bun 1.2.15 and 1.4.1):
8 passed, 0 failed. `runs with npm present` performs a real `npm install
@coder/xum@next --ignore-scripts` in `node:20-alpine`; `runs with
default` exercises the scoped tarball fallback in `alpine/curl`.
- `scripts/terraform_validate.sh`, `scripts/shellcheck_validate.sh`,
`scripts/validate_set_u_order.sh`, `bun fmt:ci`, and
`cmd/readmevalidation` all clean.
- Manual end-to-end check in a scratch directory: `bun add
--ignore-scripts @coder/xum@next` resolved `0.28.4-next.3.g2a1b5b267`;
`node_modules/.bin/mux --version` printed
`v0.28.5-nightly.5-3-g2a1b5b267-dirty (2a1b5b267)`; `mux server --port
4321` answered `GET /health` with 200 within 3 seconds. Skipping
`@coder/xum`'s `postinstall` (`sh scripts/postinstall.sh`) is safe here:
the script only rebuilds node-pty/duckdb for Electron and exits early in
server mode or when installed as a dependency.
- Registry check: `GET https://registry.npmjs.org/@coder%2Fxum/next`
returns 200 and its `dist.tarball` equals the URL the fallback
constructs.

## Related Issues

None

> Xum acted on behalf of @ibetitsmike for this PR.

Generated with Xum using Claude.
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