multica-ai/multica · error
clear hermes skills dir: %w
Error message
clear hermes skills dir: %w
What it means
writeHermesBoundSkills could not os.RemoveAll the task-local skills/ directory before rebuilding it from scratch. The rebuild-from-empty design guarantees a skill removed since the last run cannot linger; a failed wipe therefore fails the overlay instead of risking a stale skill winning a name collision.
Source
Thrown at server/internal/daemon/execenv/hermes_home.go:787
}
add(filepath.Clean(entry))
}
// The shared skills dir, referenced (not copied) so the user's global and
// builtin skills stay visible. It differs from the task-local skills dir,
// so Hermes won't fold it into the local root, and being last it yields to
// the bound skills on a name collision.
add(filepath.Join(sharedHome, "skills"))
return out
}
// writeHermesBoundSkills rebuilds the task-local skills/ dir from scratch so a
// skill removed since the last run can't linger, then writes only the
// Multica-bound skills. They keep their natural slug (no user skills share this
// dir) and therefore take precedence over any same-named external skill.
func writeHermesBoundSkills(hermesHome string, workspaceSkills []SkillContextForEnv, logger *slog.Logger) error {
skillsDir := filepath.Join(hermesHome, "skills")
if err := os.RemoveAll(skillsDir); err != nil {
return fmt.Errorf("clear hermes skills dir: %w", err)
}
if len(workspaceSkills) == 0 {
// Defensive: callers gate on a non-empty set, but stay correct if that
// ever changes — an empty local dir just means the external root is the
// only source, matching un-redirected behavior.
return os.MkdirAll(skillsDir, 0o700)
}
// Skills live under env.RootDir/hermes-home, which the GC loop (cloud) or
// env teardown (local_directory) wipes wholesale — no sidecar manifest.
return writeSkillFiles(skillsDir, workspaceSkills, nil)
}
// existingHermesExternalDirs reads the raw skills.external_dirs entries from a
// parsed config document, accepting either a single string or a list (both of
// which Hermes accepts). Returns nil when absent.
func existingHermesExternalDirs(doc *yaml.Node) []string {
top := yamlDocumentRoot(doc)
if top == nil {View on GitHub (pinned to 2c0912b6ec)
Solutions
- Inspect <hermesHome>/skills ownership and permissions; fix so the daemon UID can remove it.
- On Windows, exclude the env RootDir from AV/indexing/sync tools, or stop the tool holding handles, then retry.
- Delete the skills dir manually (it is fully regenerated) or wipe the whole overlay.
- Ensure a single daemon instance owns a given env RootDir at a time.
Defensive patterns
Strategy: try-catch
Try / catch
if err := writeHermesBoundSkills(hermesHome, skills, logger); err != nil {
if strings.Contains(err.Error(), "clear hermes skills dir") {
// wipe retry once: skills dir is fully rebuilt from the skill set
if os.RemoveAll(skillsDir) == nil {
return writeHermesBoundSkills(hermesHome, skills, logger)
}
}
return err
} Prevention
- On Windows, keep watchers/AV off the env root so skills/ can be wiped.
- Rebuild-from-scratch semantics mean the dir must always be deletable — never mark it read-only.
- One writer per env RootDir at a time.
When it happens
Trigger: skills/ (or something inside it) is undeletable: read-only or foreign-owned files, a Windows handle held open by a watcher/indexer/AV scanning skill files, or an immutable flag.
Common situations: Windows file-watching tools (editors, Dropbox, AV) pinning skill files while a task starts; overlay dirs touched by root during debugging; concurrent tasks sharing one env RootDir.
Related errors
- remove legacy task state %s: %w
- mirror %s: %w
- reconcile stale %s: %w
- remove stale %s: %w
- rename temp to %s: %w
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/947fc0fc0a1e358e.
Report an issue: GitHub.