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

  1. Check the file as the daemon user: `cat <sharedHome>/config.yaml` — fix chmod/chown so it is readable.
  2. Verify HERMES_HOME resolves to the intended home (the same home `hermes` CLI uses interactively); misresolution is exactly what this error surfaces.
  3. If config.yaml is a broken/unreadable symlink, repair or delete it (absent config is handled gracefully).
  4. 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

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


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/62f79f8685eeabcc. Report an issue: GitHub.