multica-ai/multica · error
marshal hermes config: %w
Error message
marshal hermes config: %w
What it means
marshalYAMLToFile failed to yaml.Marshal the in-memory config node tree. With a Node-based document this is rare: it indicates content the encoder cannot represent (invalid character data, incompatible tag/type on a node) rather than a filesystem problem — the write happens afterwards.
Source
Thrown at server/internal/daemon/execenv/hermes_home.go:905
val,
)
}
// yamlStringSeq builds a YAML sequence node of string scalars.
func yamlStringSeq(vals []string) *yaml.Node {
seq := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
for _, v := range vals {
seq.Content = append(seq.Content, &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: v})
}
return seq
}
// marshalYAMLToFile renders a YAML node to dst as a 0600 file (it can hold
// inline secrets) via atomic replace.
func marshalYAMLToFile(doc *yaml.Node, dst string) error {
out, err := yaml.Marshal(doc)
if err != nil {
return fmt.Errorf("marshal hermes config: %w", err)
}
return writeFileAtomic(dst, out, 0o600)
}
// writeFileAtomic writes data to a temp file in the destination directory with
// the given perms, then renames it over dst — so readers never see a partial
// file and a prior file's looser permissions are replaced.
func writeFileAtomic(dst string, data []byte, perm os.FileMode) error {
dir := filepath.Dir(dst)
tmp, err := os.CreateTemp(dir, ".hermes-tmp-*")
if err != nil {
return fmt.Errorf("create temp for %s: %w", dst, err)
}
tmpName := tmp.Name()
defer os.Remove(tmpName) // no-op once renamed
if _, err := tmp.Write(data); err != nil {
tmp.Close()
return fmt.Errorf("write temp for %s: %w", dst, err)View on GitHub (pinned to 2c0912b6ec)
Solutions
- Inspect config.yaml for custom tags (!!python/..., !binary), anchors/aliases, or non-UTF-8 bytes; simplify them.
- Re-save the config with a standard YAML writer to normalize it.
- Copy the user's essential settings (model, api_key) into a fresh minimal config.yaml and delete the exotic one.
- If unfixable, note the code copies the config verbatim only on parse failure — a file that parses but cannot re-marshal must be simplified by hand.
Defensive patterns
Strategy: try-catch
Try / catch
if err := marshalYAMLToFile(&doc, dstConfig); err != nil {
if strings.Contains(err.Error(), "marshal hermes config") {
// encoder rejected exotic nodes: fall back to verbatim copy of the source
return writeFileAtomic(dstConfig, srcData, 0o600)
}
return err
} Prevention
- Avoid custom YAML tags and binary scalars in ~/.hermes/config.yaml.
- Regenerate suspect configs with a standard YAML writer to normalize node shapes.
When it happens
Trigger: A config.yaml that parsed into nodes the v3 encoder rejects on re-marshal — unusual anchors/aliases or tags produced by other YAML tooling, or corrupted scalar values with invalid UTF-8.
Common situations: Exotic hand-crafted YAML in ~/.hermes/config.yaml (custom local tags like !!python/object, binary scalars) written by Python tooling; extremely old or nonstandard YAML generators.
Related errors
- hermes config: unexpected root node
- mint PAT: target API URL not set
- runtime local skill discovery failed
- read shared config: %w
- Invalid desktop runtime config: ${field} must be a non-empty
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/0a80bf77aebf0ea3.
Report an issue: GitHub.