multica-ai/multica · error
hermes config: unexpected root node
Error message
hermes config: unexpected root node
What it means
setHermesExternalDirs parsed the user's config.yaml successfully but the document's root node is not a YAML mapping (yamlDocumentRoot returned nil — e.g. the file is a top-level sequence, scalar, or empty document). skills.external_dirs can only be set on a mapping root, so the derived-config step refuses to proceed.
Source
Thrown at server/internal/daemon/execenv/hermes_home.go:833
}
if ed.Kind != yaml.SequenceNode {
return nil
}
out := make([]string, 0, len(ed.Content))
for _, c := range ed.Content {
if c.Kind == yaml.ScalarNode {
out = append(out, c.Value)
}
}
return out
}
// setHermesExternalDirs sets skills.external_dirs on the config document,
// creating the skills mapping if needed and preserving every other setting.
func setHermesExternalDirs(doc *yaml.Node, dirs []string) error {
top := yamlDocumentRoot(doc)
if top == nil {
return fmt.Errorf("hermes config: unexpected root node")
}
skills := yamlMapValue(top, "skills")
if skills == nil || skills.Kind != yaml.MappingNode {
skills = &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
yamlSetMapValue(top, "skills", skills)
}
yamlSetMapValue(skills, "external_dirs", yamlStringSeq(dirs))
return nil
}
// yamlDocumentRoot returns the top-level mapping node of a parsed document, or
// nil if the shape isn't a mapping.
func yamlDocumentRoot(doc *yaml.Node) *yaml.Node {
if doc == nil {
return nil
}
node := doc
if node.Kind == yaml.DocumentNode {View on GitHub (pinned to 2c0912b6ec)
Solutions
- Open the shared home's config.yaml and check the top level is a mapping of key: value pairs (no leading '-').
- Validate with a YAML linter or `python -c "import yaml,sys; d=yaml.safe_load(open(...)); print(type(d))"` — it must be a dict.
- Fix or replace the config with a valid Hermes config; if none is wanted, deleting config.yaml is fine (missing config is a supported path).
- Rerun the task after fixing.
Example fix
# before (~/.hermes/config.yaml — invalid, top-level sequence)
- model: gpt-4
- skills:
external_dirs: [/x]
# after
model: gpt-4
skills:
external_dirs:
- /x Defensive patterns
Strategy: validation
Validate before calling
var doc yaml.Node
if err := yaml.Unmarshal(data, &doc); err != nil { /* handled by verbatim-copy path */ }
if root := yamlDocumentRoot(&doc); root == nil {
return fmt.Errorf("config.yaml root is not a mapping — fix %s", srcConfig)
} Type guard
func isMappingRoot(doc *yaml.Node) bool {
return doc != nil && yamlDocumentRoot(doc) != nil
} Try / catch
if err := setHermesExternalDirs(&doc, dirs); err != nil {
if errors.Is(err, errHermesConfigRoot) || strings.Contains(err.Error(), "unexpected root node") {
// degrade gracefully: copy user config verbatim, log which file is malformed
return writeFileAtomic(dstConfig, data, 0o600)
}
return err
} Prevention
- Lint user config.yaml for a mapping root before enabling Hermes tasks.
- Automated checks: `yaml.safe_load` returning a dict is the shape this code requires.
When it happens
Trigger: A config.yaml whose top level is a list ('- a\n- b'), a bare scalar ('hello'), or an effectively empty document reaches writeDerivedHermesConfig and the parse succeeds but yields a non-mapping root.
Common situations: Hand-edited or template-generated config.yaml with wrong structure; a config meant for a different tool pasted into ~/.hermes/config.yaml; truncated multi-document YAML where the first document is a flow sequence.
Related errors
- marshal hermes config: %w
- Invalid desktop runtime config: ${field} must use http or ht
- Invalid desktop runtime config: ${field} must use ws or wss
- mint PAT: target API URL not set
- runtime local skill discovery failed
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/08aa207c5127abde.
Report an issue: GitHub.