Skip to content

fix: watch providers.jsonc for external edits so UI refreshes without restart#3233

Open
Neppkun wants to merge 5 commits into
coder:mainfrom
Neppkun:claude/gracious-vaughan-6da90f
Open

fix: watch providers.jsonc for external edits so UI refreshes without restart#3233
Neppkun wants to merge 5 commits into
coder:mainfrom
Neppkun:claude/gracious-vaughan-6da90f

Conversation

@Neppkun
Copy link
Copy Markdown

@Neppkun Neppkun commented May 5, 2026

Summary

  • Custom providers added manually to ~/.mux/providers.jsonc never appeared in the UI because notifyConfigChanged() was only called after API mutations, not on external file changes
  • Added Config.watchProvidersFile() — watches the mux home directory with fs.watch, debounced 300 ms, returns a cleanup fn
  • Wired it into ProviderService constructor so any external edit to providers.jsonc immediately propagates to all onConfigChanged frontend subscribers

Root cause

ProviderService.list() and getConfig() already read providers.jsonc fresh on every call, so the backend always had the right data. The missing piece was a signal to the frontend to re-fetch. notifyConfigChanged() emits that signal — but it was only called after in-app edits, leaving manual file edits invisible until restart.

Test plan

  • Add a custom provider entry to ~/.mux/providers.jsonc while the app is running — it should appear in Settings → Providers within ~300 ms, no restart needed
  • Remove the entry from the file — it should disappear from the UI within ~300 ms
  • Verify built-in provider changes via the UI still work as before
  • Confirm no MaxListenersExceededWarning noise in dev logs

🤖 Generated with Claude Code

…ut restart

Manual edits to ~/.mux/providers.jsonc were silently ignored by the
frontend because notifyConfigChanged() was only called after API
mutations. Added watchProvidersFile() to Config (fs.watch on the mux
home directory, debounced 300ms) and wired it into ProviderService's
constructor so external file changes propagate to all onConfigChanged
subscribers automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Neppkun
Copy link
Copy Markdown
Author

Neppkun commented May 5, 2026

I apologize in advance for using Claude Code to write this instead of Mux. (I ran out of credits)
Tested the changes on web UI, works as intended.
image
image
image

@ethanndickson
Copy link
Copy Markdown
Member

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a 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: 902766a042

ℹ️ 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 src/node/config.ts Outdated
Comment thread src/node/services/providerService.ts Outdated
Neppkun and others added 2 commits May 25, 2026 14:07
…oop handle

Two CI failures caused by the providers.jsonc watcher:
- Smoke test crashed with ENOENT because ~/.mux doesn't exist on a fresh
  install; now ensurePrivateDirSync is called before fs.watch.
- Integration tests hung and tore down with "Jest environment has been
  torn down" errors because the persistent watcher kept the event loop
  alive; fixed by passing persistent: false to fs.watch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The persistent: false flag was sufficient on Linux/macOS but Windows
Jest workers still flagged "failed to exit gracefully" after my second
commit, because the watcher's FSWatcher handle was never closed when
tests tore down their ServiceContainer.

Store the stop-watching callback returned by Config.watchProvidersFile,
expose ProviderService.dispose() that calls it and clears emitter
listeners, and wire it into ServiceContainer.dispose() alongside the
other service disposals.

Verified locally:
- providerService unit tests: 75/75 pass
- Standalone repro confirms the watcher fires on edits and disposal
  removes all active handles
- Typecheck and lint clean on changed files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Neppkun
Copy link
Copy Markdown
Author

Neppkun commented May 25, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@ethanndickson
Copy link
Copy Markdown
Member

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a 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: 8a9efb3226

ℹ️ 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 src/node/config.ts Outdated
Comment thread src/node/config.ts Outdated
Two issues Codex flagged on the watcher:

P1 — fs.watch can throw for reasons beyond ENOENT: unsupported network
filesystems (NFS/SMB), virtualized mounts, or watch-limit exhaustion
(ENOSPC on Linux). Wrap the call in try/catch, log a warning, and
return a no-op cleanup. The app boots and provider config keeps
working; users on those filesystems just lose live-refresh (which is
the pre-PR behaviour).

P2 — fs.watch may pass changedFilename=null on some platforms/kernels
(notably older macOS FSEvents). The previous "changedFilename !==
filename" guard silently dropped valid events in that case, defeating
the feature for exactly the edge it's meant to fix. Treat null as
"unknown — might be ours" and fire the debounced callback; the
worst case is one extra re-fetch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ethanndickson
Copy link
Copy Markdown
Member

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a 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: 50aed63577

ℹ️ 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 src/node/config.ts Outdated
Codex flagged that ensurePrivateDirSync was outside the try/catch
guarding the watcher, so a read-only filesystem or unwritable MUX_ROOT
would still throw out of ProviderService construction and crash
startup. Move directory creation inside the same try/catch so every
failure path degrades to the documented "manual edits need restart"
behavior instead of taking the app down.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Neppkun
Copy link
Copy Markdown
Author

Neppkun commented May 25, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

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