multica-ai/multica · error

mirror %s: %w

Error message

mirror %s: %w

What it means

While mirroring one entry from the shared Hermes home into the task overlay, linkSharedHermesEntry failed for that named entry. This wraps any of the per-entry failures: removing a stale destination ('remove stale'), stat-ing the source ('stat'), or creating the symlink itself (createDirLink/createFileLink, plain os.Symlink on POSIX, copy fallback on Windows). The %s names the entry (e.g. .env, auth.json).

Source

Thrown at server/internal/daemon/execenv/hermes_home.go:538

	entries, err := os.ReadDir(sharedHome)
	if err != nil {
		if os.IsNotExist(err) {
			// No shared home to mirror. The derived config + bound skills still
			// give Hermes a working home, so this is not fatal on its own.
			return reconcileMirroredEntries(hermesHome, nil)
		}
		return fmt.Errorf("read shared home: %w", err)
	}
	mirrored := make(map[string]struct{}, len(entries))
	for _, entry := range entries {
		name := entry.Name()
		if isHermesOverlayOwnedEntry(name) {
			continue
		}
		src := filepath.Join(sharedHome, name)
		dst := filepath.Join(hermesHome, name)
		if err := linkSharedHermesEntry(src, dst); err != nil {
			return fmt.Errorf("mirror %s: %w", name, err)
		}
		mirrored[name] = struct{}{}
	}
	return reconcileMirroredEntries(hermesHome, mirrored)
}

// reconcileMirroredEntries removes overlay entries that are neither overlay-owned
// nor currently mirrored from the shared home, so a shared entry deleted between
// runs (or a Windows copy-fallback left behind) doesn't survive as stale state.
func reconcileMirroredEntries(hermesHome string, mirrored map[string]struct{}) error {
	entries, err := os.ReadDir(hermesHome)
	if err != nil {
		return fmt.Errorf("read overlay home: %w", err)
	}
	for _, entry := range entries {
		name := entry.Name()
		if isHermesOverlayOwnedEntry(name) {
			continue

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Inspect the named entry in both the shared home and the overlay dir: `ls -la` both paths, look for root-owned or read-only leftovers.
  2. Delete the stale overlay entry (or the whole env overlay dir under RootDir/hermes-home) so the next prepare recreates it cleanly.
  3. On Windows, enable Developer Mode or run the daemon with symlink privilege so os.Symlink works.
  4. Ensure only one daemon instance prepares a given env at a time; two writers racing on one overlay is not supported.
Defensive patterns

Strategy: try-catch

Try / catch

if err := mirrorSharedHermesHome(sharedHome, hermesHome, logger); err != nil {
	if strings.HasPrefix(err.Error(), "mirror ") {
		// one entry is bad: wipe the regenerable overlay and retry once
		if rmErr := os.RemoveAll(hermesHome); rmErr == nil {
			return mirrorSharedHermesHome(sharedHome, hermesHome, logger)
		}
	}
	return err
}

Prevention

When it happens

Trigger: A specific entry in the shared home is unreadable or its existing overlay counterpart is undeletable: dst left root-owned from a previous privileged run; Windows symlink creation without Developer Mode/privilege; EEXIST race with a concurrent task reusing the same overlay; source entry is a symlink loop.

Common situations: Mixed-privilege daemons (some tasks ran as root, later ones as a normal user); Windows hosts without symlink privilege; two daemon replicas preparing the same env RootDir concurrently; an antivirus/backup tool briefly holding the entry.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/b8f78f4599326a9c. Report an issue: GitHub.