Skip to content

feat(module): let modules declare per-workspace bind mounts - #49

Open
Banh-Canh wants to merge 2 commits into
Mickael-Roger:mainfrom
Banh-Canh:feat/module-mounts
Open

Banh-Canh wants to merge 2 commits into
Mickael-Roger:mainfrom
Banh-Canh:feat/module-mounts

Conversation

@Banh-Canh

Copy link
Copy Markdown
Contributor

Summary

A module.yml can now declare mounts: host paths bind-mounted into the containers of the workspaces that have the module installed, and only those. The global extraMounts applies to every workspace; this lets access to a host path be granted per workspace from the module editor, e.g. sharing a host login rather than copying it through resolve.

name: claude-auth-shared
version: 1
description: Share this host's Claude Code login with the workspace.
mounts:
  - source: ~/.claude/.credentials.json
    target: /home/debian/.claude/.credentials.json
    optional: true

Fields: source (absolute, or ~/-relative to the host home), target (absolute; not / or the workspace home), readOnly, optional (skip while source is missing instead of failing the start; the mount is added on the first start after it appears).

How it works

  • provision collects the mounts of the manifest's installed modules (installedModuleMounts) and records a fingerprint in OCM_MODULE_MOUNTS_SHA256, so containerSpecDrift recreates the container when the set changes. They are added before the self-improvement mounts, so its conflict check covers them.
  • AddModule records a mounting module in the manifest before starting the container, so its install already sees the mounts; the entry is rolled back if the install fails.
  • RemoveModule recreates the container after the uninstall, so access ends right away.
  • Mount points inside the workspace home are created on the host first (empty file or directory, owned by the user), so the runtime never creates them as root.
  • Recreation interrupts a running task, so a module with mounts cannot set restartServer: false (rejected at load). The editor's idle guard then applies as for any restarting module.

Tests / docs

  • internal/module: parsing, ~/ expansion, validation errors.
  • internal/workspace: mounts only for installed modules, optional sources, fingerprint changes, mount point creation, required-source failure, AddModule ordering, drift cases.
  • docs/writing-modules.md gains a Mounts section; AGENTS.md documents the lifecycle.

gofmt, go vet ./... and go test ./... pass. Used with podman on NixOS through a fork build to share the host Claude Code login with selected workspaces.

🤖 Generated with Claude Code

@Mickael-Roger

Copy link
Copy Markdown
Owner

Blocking issue: a failed add of a mounting module can leave the host path mounted in a running workspace even though the manifest rollback removes the module.

addMountingModule saves the module in the manifest, starts/recreates the container with its mount, then rolls only the manifest back if ensureStarted, value resolution, or the install script fails. It does not recreate or remove that already-running container. The code comments that the stray mount disappears on the next start, but until then the workspace retains access to the host path without any installed module granting it.

Please reconcile the container immediately after rollback so its mount set matches the restored manifest, and add a regression test that fails installation after the container is created and verifies the failed module mount is absent from the resulting container.

Banh-Canh and others added 2 commits September 28, 2026 22:26
A module.yml can now list `mounts` (host `source`, absolute or `~/`-relative,
container `target`, `readOnly`, `optional`). They are bind-mounted only into
the containers of the workspaces that have the module installed, unlike the
global `extraMounts`, so access to a host path — e.g. a login shared with
the host rather than copied — can be granted per workspace.

The mounts are fingerprinted into OCM_MODULE_MOUNTS_SHA256, so the container
is recreated when the set changes. AddModule records such a module before
starting the container, so its install already sees the mounts (and rolls the
entry back if it fails); RemoveModule recreates the container after the
uninstall. Mount points in the workspace home are created on the host first.
A module with mounts cannot set restartServer: false, since recreation
interrupts a running task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed add of a module with mounts rolled back the manifest but left the
container it had recreated with the module's mounts running, so the
workspace kept access to the host path until the next start. Reconcile the
container with the restored manifest after the rollback, or stop it when
that fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Banh-Canh

Copy link
Copy Markdown
Contributor Author

Thanks, fixed: after the manifest rollback, the container is now reconciled with the restored manifest right away, so it is recreated without the failed module's mounts (and stopped if that fails). Added a regression test that fails the install after the container is created and checks the mount is gone from the resulting container. Also rebased on main.

This branch has not been deployed

No deployments
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