getsops/sops · error

Error encoding section: Section values should always be Tree

Error message

Error encoding section: Section values should always be TreeBranches

What it means

In the INI store, every section's value must be a sops.TreeBranch (the list of key/value items inside that section). encodeTree type-asserts item.Value.(sops.TreeBranch); if the value is a scalar, array, or any other type, this error fires because INI has no way to represent a section whose body is not a key list.

Source

Thrown at stores/ini/store.go:42

func (store *Store) Name() string {
	return "ini"
}

func (store Store) encodeTree(branches sops.TreeBranches) ([]byte, error) {
	iniFile := ini.Empty(ini.LoadOptions{AllowNonUniqueSections: true})
	iniFile.DeleteSection(ini.DefaultSection)
	for _, branch := range branches {
		for _, item := range branch {
			if _, ok := item.Key.(sops.Comment); ok {
				continue
			}
			section, err := iniFile.NewSection(item.Key.(string))
			if err != nil {
				return nil, fmt.Errorf("Error encoding section %s: %s", item.Key, err)
			}
			itemTree, ok := item.Value.(sops.TreeBranch)
			if !ok {
				return nil, fmt.Errorf("Error encoding section: Section values should always be TreeBranches")
			}

			first := 0
			if len(itemTree) > 0 {
				if sectionComment, ok := itemTree[0].Key.(sops.Comment); ok {
					section.Comment = sectionComment.Value
					first = 1
				}
			}

			var lastItem *ini.Key
			for i := first; i < len(itemTree); i++ {
				keyVal := itemTree[i]
				if comment, ok := keyVal.Key.(sops.Comment); ok {
					if lastItem != nil {
						lastItem.Comment = comment.Value
					}
				} else {

View on GitHub (pinned to 13442bb981)

Solutions

  1. Wrap section values in sops.TreeBranch: move the scalar under a key, e.g. {Key: "settings", Value: sops.TreeBranch{{Key: "value", Value: "plain string"}}}
  2. Convert arrays to a TreeBranch of indexed keys ("0", "1", ...) or pick a list-capable store (json/yaml)
  3. Validate the tree shape (every top-level value is TreeBranch) before calling the INI store's emit functions

Example fix

// before
sops.TreeBranch{{Key: "settings", Value: "plain string"}}

// after
sops.TreeBranch{{Key: "settings", Value: sops.TreeBranch{{Key: "value", Value: "plain string"}}}}
Defensive patterns

Strategy: type-guard

Validate before calling

for _, item := range branch {
	if _, ok := item.Value.(sops.TreeBranch); !ok {
		return fmt.Errorf("section %v value must be a TreeBranch, got %T", item.Key, item.Value)
	}
}

Type guard

func isTreeBranch(v interface{}) bool {
	_, ok := v.(sops.TreeBranch)
	return ok
}

Try / catch

out, err := store.EmitPlainFile(branches)
if err != nil {
	if strings.Contains(err.Error(), "should always be TreeBranches") {
		return fmt.Errorf("restructure tree: wrap section values in sops.TreeBranch: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling EmitEncryptedFile/EmitPlainFile with a top-level TreeItem whose value is not sops.TreeBranch — e.g. {Key: "settings", Value: "plain string"} or {Key: "arr", Value: []interface{}{...}}.

Common situations: Converting flat key=value data or JSON/YAML trees with scalar top-level values into INI; programmatic tree building that forgot to nest section keys under a TreeBranch; reusing branches decoded from a different store format without restructuring.

Related errors


AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01). Data as JSON: /api/errors/a553a09beb5c3bfa. Report an issue: GitHub.