multica-ai/multica · error

create hermes memory store %s: %w

Error message

create hermes memory store %s: %w

What it means

mountHermesMemories found the overlay's memories/ already symlinked to the intended store directory, but os.MkdirAll(storeDir, 0700) failed while ensuring the store exists. The per-agent memory store lives outside the task overlay so memory outlives tasks; the daemon both validates the link target and materializes the store in one idempotent step.

Source

Thrown at server/internal/daemon/execenv/hermes_memory.go:154

}

// mountHermesMemories points the overlay's memories/ at storeDir, so the agent's
// memory outlives the task. Idempotent across Reuse: a link already pointing at
// the store is left alone.
//
// A real memories/ directory left by an older daemon (or by a task that ran
// without a store to key on) is migrated into the store rather than discarded —
// but only into an empty store, so an upgrade never overwrites memory the agent
// has already accumulated. A migration that cannot complete fails the overlay
// rather than dropping the directory it could not carry over.
func mountHermesMemories(hermesHome, storeDir string, logger *slog.Logger) error {
	dst := filepath.Join(hermesHome, hermesMemoriesEntry)

	if fi, err := os.Lstat(dst); err == nil {
		if fi.Mode()&os.ModeSymlink != 0 {
			if target, rlErr := os.Readlink(dst); rlErr == nil && filepath.Clean(target) == filepath.Clean(storeDir) {
				if err := os.MkdirAll(storeDir, 0o700); err != nil {
					return fmt.Errorf("create hermes memory store %s: %w", storeDir, err)
				}
				touchHermesMemoryStore(storeDir, logger)
				return nil
			}
		} else if fi.IsDir() {
			// Runs before the store dir is created, so migration can publish a
			// fully-copied tree with one atomic rename. Fails closed: the source
			// dir below is only removed once every entry is safely in the store.
			if err := migrateHermesTaskMemories(dst, storeDir, logger); err != nil {
				return err
			}
		}
		if err := os.RemoveAll(dst); err != nil {
			return fmt.Errorf("remove stale memories path %s: %w", dst, err)
		}
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("stat memories path %s: %w", dst, err)
	}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Check the storeDir from the error: verify each parent exists, is a directory, and is writable by the daemon user.
  2. Remove any regular file occupying a component of the store path, then retry.
  3. Free disk space / fix the read-only mount on the volume holding the profile dir.
  4. Ensure the daemon runs as the user who owns the Multica profile directory.
Defensive patterns

Strategy: validation

Validate before calling

if err := ensureWritableDir(filepath.Dir(storeDir)); err != nil {
	return fmt.Errorf("memory store parent unusable: %w", err)
}
if fi, err := os.Stat(storeDir); err == nil && !fi.IsDir() {
	return fmt.Errorf("store path occupied by non-directory: %s", storeDir)
}

Try / catch

if err := mountHermesMemories(hermesHome, storeDir, logger); err != nil {
	var pe *os.PathError
	if errors.As(err, &pe) && strings.Contains(err.Error(), "create hermes memory store") {
		log.Printf("memory store %s cannot be created — check ownership/space of the profile dir", storeDir)
	}
	return err
}

Prevention

When it happens

Trigger: The store path's parent (Multica profile dir) is unwritable or owned by another user; a path component of storeDir is an existing regular file; ENOSPC or read-only filesystem at store creation; Windows path/ACL problems.

Common situations: Daemon and desktop user mismatch on the profile directory; profile dir on a full or read-only volume; a stray file occupying a directory name in the store path (e.g. a file named like the store hash dir); migrated setups where the profile dir changed location.

Related errors


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