getsops/sops · error
Found non-string key %q when unflattening
Error message
Found non-string key %q when unflattening
What it means
unflattenTreeBranch converts a flattened TreeBranch (dotted paths like "a__map_b__list_0") back into a nested sops.TreeBranch. It expects every item's Key to be a Go string produced by Flatten. When a key is neither a sops.Comment nor a string (e.g. int, bool, sops.Comment is skipped, but other types pass through), this error aborts the round-trip because tokenize() cannot build a path from a non-string key.
Source
Thrown at stores/flatten.go:189
result[k] = value
}
return result, nil
}
func unflattenTreeBranch(branch sops.TreeBranch) (sops.TreeBranch, error) {
root := &node{}
for _, item := range branch {
if _, ok := item.Key.(sops.Comment); ok {
continue
}
if key, ok := item.Key.(string); ok {
tokens := tokenize(key)
err := place(&item.Value, tokens, root)
if err != nil {
return nil, fmt.Errorf("Error while unflattening %q: %w", key, err)
}
} else {
return nil, fmt.Errorf("Found non-string key %q when unflattening", item.Key)
}
}
result, err := convert(root)
if err != nil {
return nil, fmt.Errorf("Error while unflattening: %w", err)
}
if tb, ok := result.(sops.TreeBranch); ok {
return tb, nil
}
return nil, fmt.Errorf("Internal error: cannot find root")
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Flatten
func flattenDescendValue(value interface{}, key string, destination sops.TreeBranch, destinationMap *map[string]bool) (sops.TreeBranch, error) {
switch value := value.(type) {
case sops.TreeBranch:View on GitHub (pinned to 13442bb981)
Solutions
- Convert every top-level key to a string before calling unflattenTreeBranch/ExtractMetadata (e.g. fmt.Sprintf("%v", key))
- Wrap numeric keys used for array positions in the expected flattened syntax (a__list_0) instead of using raw non-string keys
- Skip or drop non-string items from the branch, converting them to sops.Comment if they are comments
- Load the data through the store's LoadPlainFile/LoadEncryptedFile so keys are normalized to strings
Example fix
// before
branch := sops.TreeBranch{{Key: 1, Value: "x"}}
_, err := stores.ExtractMetadata(sops.TreeBranches{branch}, opts)
// after
branch := sops.TreeBranch{{Key: "1", Value: "x"}}
_, err := stores.ExtractMetadata(sops.TreeBranches{branch}, opts) Defensive patterns
Strategy: validation
Validate before calling
for _, item := range branch {
if _, ok := item.Key.(sops.Comment); ok {
continue
}
if _, ok := item.Key.(string); !ok {
return fmt.Errorf("top-level key %v is not a string", item.Key)
}
} Type guard
func isStringKey(key interface{}) bool {
_, ok := key.(string)
return ok
} Try / catch
tb, err := unflattenTreeBranch(branch)
if err != nil {
if strings.Contains(err.Error(), "non-string key") {
// normalize keys to strings and retry
}
return err
} Prevention
- Always build TreeBranches with string keys
- Convert numeric/other key types with fmt.Sprintf before use
- Use the store Load* functions instead of hand-assembling trees
When it happens
Trigger: Calling stores.ExtractMetadata (with Flatten enabled) on TreeBranches whose sops branch contains top-level TreeItem keys that are not strings and not sops.Comment values — for example a TreeItem{Key: 42, Value: ...} or a key produced programmatically as an int.
Common situations: Hand-built TreeBranches passed to sops instead of data loaded from a real store; custom store implementations that emit numeric keys (like JSON/YAML do for array indices) fed into the INI-related metadata extraction path; programmatic tree construction where keys were not normalized to strings via fmt.Sprintf.
Related errors
- Found non-string key %q when flattening
- Error while unflattening: %w
- Found key collision %q while flattening
- Error encoding section: Section values should always be Tree
- invalid %s key configuration: expected string in list, got %
AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01).
Data as JSON: /api/errors/baedd7b154fedf11.
Report an issue: GitHub.