Per-project devShell VSCodium with project-specific extensions + shared home-manager user settings

Disclosure: I worked through this with an AI assistant. The behavioural findings below are things I actually tested on my machine and confirmed.

Setup

Several repos, each with a flake default devShell providing VSCodium (via vscode-with-extensions) with project-specific extensions. Goal: entering a project’s shell gives an editor with exactly that project’s extensions, reproducibly, while developer-level user settings (font size, theme, keybindings) stay shared across all projects and are owned by home-manager.

Problem

Open VSCodium from project A, then project B while A is open → B’s window shows A’s extensions. Only the first-opened project’s extension set applies to every window.

Cause (assistant’s analysis, unverified by me)

A VSCodium “instance” is keyed on --user-data-dir, not --extensions-dir. The second launch finds the first instance’s IPC socket and delegates “open folder” to it instead of starting a new main process, so its --extensions-dir is ignored and it inherits the first instance’s extension host. Forcing a distinct --user-data-dir per project makes them separate instances.

Solution that worked for me

A personal wrapper (lives on the flake side, since a devShell is evaluated by the project flake — home-manager can’t inject into it). It gives each project its own data dir but symlinks the human-authored config from home-manager’s real VSCodium user dir so settings stay shared:

codiumWrapped = pkgs.writeShellScriptBin "codium" ''
  set -euo pipefail

  # per-project data dir -> separate instance -> its own extensions
  data_dir="$PWD/.vscodium/user-data"
  mkdir -p "$data_dir/User"

  # home-manager's ACTUAL VSCodium user dir (Linux; macOS differs)
  shared="''${XDG_CONFIG_HOME:-$HOME/.config}/VSCodium/User"

  # share only human-authored, read-at-startup config
  # (NOT globalStorage/state.vscdb -> avoids concurrent SQLite writes)
  for f in settings.json keybindings.json snippets; do
    [ -e "$shared/$f" ] && ln -sfn "$shared/$f" "$data_dir/User/$f"
  done

  exec ${codium}/bin/codium --user-data-dir "$data_dir" "$@"
'';

(Add .vscodium/ to .gitignore.)

What I actually tested

  • Extension isolation: works
  • Shared settings: works, but after a change wrapped instances need Reload Window (Ctrl/Cmd+R) after home-manager switch; the global instance updates live
  • Workspace Trust: isolated. It lives in per-project globalStorage/state.vscdb, which I keep per-instance on purpose. I was not willing to disable Restricted Mode (security.workspace.trust.enabled = false), so I accept a one-time trust prompt per project; because the state dir is per-project-persistent, it only prompts once per repo.

Open question

Is there a more idiomatic pattern for this than a personal symlink wrapper? Anything upstream in nix-vscode-extensions / programs.vscode that already addresses multi-instance + shared settings?

Thanks!