{"record":{"id":"dbb1d3e93e3834b7","repo":"multica-ai/multica","slug":"read-task-local-hermes-memories-s-w","errorCode":null,"errorMessage":"read task-local hermes memories %s: %w","messagePattern":"read task-local hermes memories (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/daemon/execenv/hermes_memory.go","lineNumber":222,"sourceCode":"//     half-populated store. First writer wins; the loser discards its staging\n//     and takes the winner's store, which is the same outcome as finding a\n//     store that was already populated.\n//\n// Only an absent or empty store is migrated into, so an upgrade never\n// overwrites memory the agent has already accumulated.\nfunc migrateHermesTaskMemories(taskDir, storeDir string, logger *slog.Logger) error {\n\tstored, err := os.ReadDir(storeDir)\n\tswitch {\n\tcase err == nil && len(stored) > 0:\n\t\treturn nil // store already holds this agent's memory — never overwrite it\n\tcase err != nil && !os.IsNotExist(err):\n\t\t// Treating this as \"nothing to migrate\" would delete the source below.\n\t\treturn fmt.Errorf(\"read hermes memory store %s: %w\", storeDir, err)\n\t}\n\n\tentries, err := os.ReadDir(taskDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"read task-local hermes memories %s: %w\", taskDir, err)\n\t}\n\tif len(entries) == 0 {\n\t\treturn nil\n\t}\n\n\tstaging, err := newHermesStoreStaging(storeDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(staging) // no-op once the staging dir has been promoted\n\n\tfor _, entry := range entries {\n\t\tsrc := filepath.Join(taskDir, entry.Name())\n\t\tif err := copyHermesMemoryEntry(src, filepath.Join(staging, entry.Name()), entry); err != nil {\n\t\t\treturn fmt.Errorf(\"migrate task-local hermes memory %s into %s: %w\", src, storeDir, err)\n\t\t}\n\t}\n","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/daemon/execenv/hermes_memory.go#L204-L240","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if ents, err := os.ReadDir(taskDir); err != nil {\n    return fmt.Errorf(\"task memories unreadable, fix or clear %s: %w\", taskDir, err)\n} else if len(ents) == 0 {\n    // migration is a no-op; safe to proceed\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["filesystem","hermes","migration","permissions"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}