{"record":{"id":"2f15ec9f1afa68f7","repo":"multica-ai/multica","slug":"remove-stale-s-w","errorCode":null,"errorMessage":"remove stale %s: %w","messagePattern":"remove stale (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/daemon/execenv/hermes_home.go","lineNumber":615,"sourceCode":"\t}\n\treturn writeFileAtomic(marker, []byte(\"task-local Hermes state\\n\"), 0o600)\n}\n\n// linkSharedHermesEntry symlinks dst → src, idempotent across Reuse: an existing\n// link already pointing at src is left alone; anything else is removed and\n// recreated so the overlay never drifts from the shared home. A dangling source\n// (a broken symlink in the user's home) is skipped, not failed. Directories use\n// createDirLink and files createFileLink so the Windows copy fallbacks match the\n// entry kind.\nfunc linkSharedHermesEntry(src, dst string) error {\n\tif fi, err := os.Lstat(dst); err == nil {\n\t\tif fi.Mode()&os.ModeSymlink != 0 {\n\t\t\tif target, err := os.Readlink(dst); err == nil && target == src {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tif err := os.RemoveAll(dst); err != nil {\n\t\t\treturn fmt.Errorf(\"remove stale %s: %w\", dst, err)\n\t\t}\n\t}\n\n\tinfo, err := os.Stat(src) // follow the link to decide dir vs file\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil // dangling source in the user's home — nothing to link\n\t\t}\n\t\treturn fmt.Errorf(\"stat %s: %w\", src, err)\n\t}\n\tif info.IsDir() {\n\t\treturn createDirLink(src, dst)\n\t}\n\treturn createFileLink(src, dst)\n}\n\n// writeDerivedHermesConfig writes the task-local config.yaml: the user's config\n// with `skills.external_dirs` set to their existing external dirs plus the shared","sourceCodeStart":597,"sourceCodeEnd":633,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/daemon/execenv/hermes_home.go#L597-L633","documentation":"linkSharedHermesEntry found an existing entry at the destination whose symlink does not already point at src, tried os.RemoveAll to clear it before recreating the link, and the removal failed. The function is idempotent by design (matching link left alone, anything else replaced), so this means the overlay holds an entry the daemon cannot delete.","triggerScenarios":"Destination entry is owned by another user or read-only; a running process holds it open (Windows sharing violation); immutable flag or sticky-bit dir prevents unlink; the entry is a directory the daemon lacks write permission on for recursive removal.","commonSituations":"Overlay reused across runs under different UIDs/privileges; previous task's process still running out of the overlay; antivirus or indexer briefly pinning files on Windows.","solutions":["Check ownership and mode of the named dst path: `ls -la`; chown/chmod it (or its parent) so the daemon can unlink.","Terminate any process still using the overlay entry, then retry the task.","Reset the overlay wholesale (delete hermes-home under the env RootDir) — every mirrored entry is regenerated from the shared home.","Run the daemon under one stable UID so this class of drift cannot occur."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := linkSharedHermesEntry(src, dst); err != nil {\n\tvar pe *os.PathError\n\tif errors.As(err, &pe) && errors.Is(pe.Err, syscall.EACCES) {\n\t\t// undeletable stale dst: drop whole overlay and let caller rebuild\n\t\treturn os.RemoveAll(filepath.Dir(dst))\n\t}\n\treturn err\n}","preventionTips":["Keep one daemon UID per env so every dst entry is deletable by later runs.","Close/stop all users of the overlay before reuse.","On Windows, exclude env roots from AV/indexing to avoid unlink sharing violations."],"tags":["filesystem","symlink","permissions","windows","hermes","execenv"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}