multica-ai/multica · error
read shared config: %w
Error message
read shared config: %w
What it means
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.
Source
Thrown at server/internal/daemon/execenv/hermes_home.go:648
}
// writeDerivedHermesConfig writes the task-local config.yaml: the user's config
// with `skills.external_dirs` set to their existing external dirs plus the shared
// ~/.hermes/skills, all as absolute paths. When the user has no config we still
// write a minimal one so their global skills stay reachable via the external
// root. If the config can't be parsed we copy it verbatim so auth/model settings
// survive — the bound skills still load from the task-local skills/ dir, which is
// the point of the fix; only the user's global skills would be missing. The file
// is written 0600 (it can hold inline api_key secrets) via atomic replace, so
// reuse also repairs a prior file's permissions.
func writeDerivedHermesConfig(sharedHome, hermesHome string, env map[string]string, logger *slog.Logger) error {
srcConfig := filepath.Join(sharedHome, "config.yaml")
dstConfig := filepath.Join(hermesHome, "config.yaml")
data, err := os.ReadFile(srcConfig)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("read shared config: %w", err)
}
// The overlay is about to be seeded from a home that carries no
// provider config, so the task runs with whatever the child's
// environment supplies and nothing else. That is a legitimate setup
// (an image that ships Hermes and injects OPENAI_API_KEY has no
// config.yaml and works fine), which is why this stays a warning
// rather than a hard failure — but when it is instead a source-home
// misresolution, this line is the only place the two paths can be
// told apart, and until GH #6872 it printed nothing at all. Name the
// source home: the user's own config is somewhere else, and Hermes'
// own error ("run `hermes model`") cannot say where.
if fi, statErr := os.Stat(sharedHome); statErr != nil || !fi.IsDir() {
logger.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",
"source_home", sharedHome, "overlay_home", hermesHome)
} else {
logger.Warn("execenv: hermes source home has no config.yaml; this task runs without a configured provider unless the environment supplies one",
"source_home", sharedHome, "overlay_home", hermesHome)
}View on GitHub (pinned to 2c0912b6ec)
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.
Defensive patterns
Strategy: validation
Validate before calling
srcConfig := filepath.Join(sharedHome, "config.yaml")
if fi, err := os.Stat(srcConfig); err == nil && (!fi.Mode().IsRegular() || fi.Mode().Perm()&0o400 == 0) {
return fmt.Errorf("shared config exists but is not a readable regular file: %s", srcConfig)
} Try / catch
data, err := os.ReadFile(srcConfig)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
// legitimate: no provider config in source home
} else {
log.Printf("config.yaml present but unreadable — check HERMES_HOME=%s", sharedHome)
return err
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- mint PAT: target API URL not set
- mint PAT: response missing token
- daemon profile is not resolved yet; token sync skipped
- runtime local skill discovery timed out
- runtime local skill import failed
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/62f79f8685eeabcc.
Report an issue: GitHub.