{"record":{"id":"62f79f8685eeabcc","repo":"multica-ai/multica","slug":"read-shared-config-w","errorCode":null,"errorMessage":"read shared config: %w","messagePattern":"read shared config: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/daemon/execenv/hermes_home.go","lineNumber":648,"sourceCode":"}\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\n// ~/.hermes/skills, all as absolute paths. When the user has no config we still\n// write a minimal one so their global skills stay reachable via the external\n// root. If the config can't be parsed we copy it verbatim so auth/model settings\n// survive — the bound skills still load from the task-local skills/ dir, which is\n// the point of the fix; only the user's global skills would be missing. The file\n// is written 0600 (it can hold inline api_key secrets) via atomic replace, so\n// reuse also repairs a prior file's permissions.\nfunc writeDerivedHermesConfig(sharedHome, hermesHome string, env map[string]string, logger *slog.Logger) error {\n\tsrcConfig := filepath.Join(sharedHome, \"config.yaml\")\n\tdstConfig := filepath.Join(hermesHome, \"config.yaml\")\n\n\tdata, err := os.ReadFile(srcConfig)\n\tif err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn fmt.Errorf(\"read shared config: %w\", err)\n\t\t}\n\t\t// The overlay is about to be seeded from a home that carries no\n\t\t// provider config, so the task runs with whatever the child's\n\t\t// environment supplies and nothing else. That is a legitimate setup\n\t\t// (an image that ships Hermes and injects OPENAI_API_KEY has no\n\t\t// config.yaml and works fine), which is why this stays a warning\n\t\t// rather than a hard failure — but when it is instead a source-home\n\t\t// misresolution, this line is the only place the two paths can be\n\t\t// told apart, and until GH #6872 it printed nothing at all. Name the\n\t\t// source home: the user's own config is somewhere else, and Hermes'\n\t\t// own error (\"run `hermes model`\") cannot say where.\n\t\tif fi, statErr := os.Stat(sharedHome); statErr != nil || !fi.IsDir() {\n\t\t\tlogger.Warn(\"execenv: hermes source home does not exist; this task runs without file-backed provider config or credentials, though the environment may still supply them\",\n\t\t\t\t\"source_home\", sharedHome, \"overlay_home\", hermesHome)\n\t\t} else {\n\t\t\tlogger.Warn(\"execenv: hermes source home has no config.yaml; this task runs without a configured provider unless the environment supplies one\",\n\t\t\t\t\"source_home\", sharedHome, \"overlay_home\", hermesHome)\n\t\t}","sourceCodeStart":630,"sourceCodeEnd":666,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/daemon/execenv/hermes_home.go#L630-L666","documentation":"writeDerivedHermesConfig could not os.ReadFile the shared home's config.yaml, and the failure was not 'does not exist' (a missing config is a legitimate warning-only path). The daemon needs to parse the user's config to inject skills.external_dirs; an unreadable-but-present config aborts overlay preparation. The code comments call out GH #6872: a misresolved source home is detectable only here.","triggerScenarios":"config.yaml exists but mode/ownership denies read to the daemon user; config.yaml is a directory; a symlink to an unreadable location; transient I/O error on the volume.","commonSituations":"Daemon service account differs from the desktop user owning ~/.hermes/config.yaml; configs synced with 600 perms from another machine; HERMES_HOME pointing into another user's home; credentials/enterprise DLP tools blocking reads of files that look like secrets.","solutions":["Check the file as the daemon user: `cat <sharedHome>/config.yaml` — fix chmod/chown so it is readable.","Verify HERMES_HOME resolves to the intended home (the same home `hermes` CLI uses interactively); misresolution is exactly what this error surfaces.","If config.yaml is a broken/unreadable symlink, repair or delete it (absent config is handled gracefully).","Retry after storage issues are resolved if the error is I/O-transient."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"srcConfig := filepath.Join(sharedHome, \"config.yaml\")\nif fi, err := os.Stat(srcConfig); err == nil && (!fi.Mode().IsRegular() || fi.Mode().Perm()&0o400 == 0) {\n\treturn fmt.Errorf(\"shared config exists but is not a readable regular file: %s\", srcConfig)\n}","typeGuard":null,"tryCatchPattern":"data, err := os.ReadFile(srcConfig)\nif err != nil {\n\tif errors.Is(err, fs.ErrNotExist) {\n\t\t// legitimate: no provider config in source home\n\t} else {\n\t\tlog.Printf(\"config.yaml present but unreadable — check HERMES_HOME=%s\", sharedHome)\n\t\treturn err\n\t}\n}","preventionTips":["Verify HERMES_HOME matches the home the hermes CLI itself resolves.","Keep config.yaml readable (640/600 owned by the daemon user).","Remember this error is the designated place where a misresolved source home becomes visible."],"tags":["filesystem","permissions","config","hermes","execenv"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}