multica-ai/multica · error
read task-local hermes memories
Error message
read task-local hermes memories %s: %w
What it means
Returned by migrateHermesTaskMemories when os.ReadDir on the task-local memories source dir fails. The migration cannot know which entries to copy, and skipping would mean silently dropping the agent's remembered context, so the overlay mount aborts.
Solutions
- Fix permissions on the task-local memories dir or re-run the task under the user that owns the overlay.
- If the task overlay is disposable, delete the whole task home so it is rebuilt without a legacy memories dir.
- Inspect the wrapped errno for hardware/mount errors before retrying.
Defensive patterns
Strategy: validation
Validate before calling
if ents, err := os.ReadDir(taskDir); err != nil {
return fmt.Errorf("task memories unreadable, fix or clear %s: %w", taskDir, err)
} else if len(ents) == 0 {
// migration is a no-op; safe to proceed
} Prevention
- Run tasks and daemon under the same user
- Treat task overlay dirs as owned by the daemon
- Check FS health when EIO appears in the wrapped error
When it happens
Trigger: os.ReadDir(<task-hermes-home>/memories) fails with EACCES (dir not readable by daemon user), EIO, or the path exists as something unreadable even though Lstat called it a directory earlier.
Common situations: Task overlay dirs written by a root-run daemon then read by a user-run daemon; corrupted overlay after a crash; FS-level errors on the volume backing task homes.
Related errors
- create hermes memory store parent
- read hermes memory store
- remove stale memories path
- chmod temp for
- clear empty hermes store
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/dbb1d3e93e3834b7.
Report an issue: GitHub.
Appendix: source
Thrown at server/internal/daemon/execenv/hermes_memory.go:222
// half-populated store. First writer wins; the loser discards its staging
// and takes the winner's store, which is the same outcome as finding a
// store that was already populated.
//
// Only an absent or empty store is migrated into, so an upgrade never
// overwrites memory the agent has already accumulated.
func migrateHermesTaskMemories(taskDir, storeDir string, logger *slog.Logger) error {
stored, err := os.ReadDir(storeDir)
switch {
case err == nil && len(stored) > 0:
return nil // store already holds this agent's memory — never overwrite it
case err != nil && !os.IsNotExist(err):
// Treating this as "nothing to migrate" would delete the source below.
return fmt.Errorf("read hermes memory store %s: %w", storeDir, err)
}
entries, err := os.ReadDir(taskDir)
if err != nil {
return fmt.Errorf("read task-local hermes memories %s: %w", taskDir, err)
}
if len(entries) == 0 {
return nil
}
staging, err := newHermesStoreStaging(storeDir)
if err != nil {
return err
}
defer os.RemoveAll(staging) // no-op once the staging dir has been promoted
for _, entry := range entries {
src := filepath.Join(taskDir, entry.Name())
if err := copyHermesMemoryEntry(src, filepath.Join(staging, entry.Name()), entry); err != nil {
return fmt.Errorf("migrate task-local hermes memory %s into %s: %w", src, storeDir, err)
}
}
View on GitHub (pinned to 2c0912b6ec)