OpenTelemetry Error Guide: 'cannot merge config' — Fix Collector Config Merge Failures
Fix 'cannot merge config: '[processors]' expected a map, got 'string'' when the Collector merges multiple --config sources with conflicting types.
- #opentelemetry
- #observability
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
The OpenTelemetry Collector accepts multiple configuration sources and deep-merges them in order with its confmap machinery. When two sources disagree on the shape of the same key — one supplies a map, another supplies a scalar or list — the merge cannot reconcile them and the Collector refuses to start:
Error: cannot merge config: '[processors]' expected a map, got 'string'
2026/07/12 14:22:10 collector server run finished with error: cannot merge config
A related variant appears when an environment-variable override collapses a whole block into a string:
Error: cannot merge config: '[exporters][otlp][sending_queue]' expected a map, got 'string'
The message means one --config source (a file, a URI, or an env-expanded value) provided a different YAML type for a key than another source did, so the layered merge has no consistent structure to produce.
Symptoms
- The Collector exits immediately at startup before any pipeline is built.
- Logs show
cannot merge confignaming a specific key path like[processors]or[exporters][otlp]. - The failure only appears when two or more
--configflags (or a base plus overrides) are supplied. - Running with a single config file succeeds, but the combined invocation fails.
- An env var such as
OTEL_PROCESSORSor an override snippet is set to a scalar where the base file has a block. otelcol-contrib validateon the merged set reports the same type mismatch.
Common Root Causes
- Scalar overriding a map — an override file sets
processors: batch(a string) while the base definesprocessors: { batch: {...} }(a map). - Env-var expansion collapsing a block —
${PROCESSORS}expands to a plain string, replacing a structured block with a scalar. - List vs map confusion — one source uses
exporters: [otlp](list) where the other expectsexporters: { otlp: {...} }(map) for that key. - Wrong merge order — a stripped-down override intended to patch one field instead redefines a parent key with an incompatible type.
- Copy-paste indentation drift — a mis-indented key lands one level up, turning a nested map into a sibling scalar during merge.
- Templated config injection — a config-map or Helm value renders an empty or quoted value (
"") where the base has a map.
Diagnostic Workflow
First, list exactly what sources the Collector is merging. Order matters — later --config values win, and each is deep-merged onto the previous:
# Inspect the running unit's ExecStart to see every --config source
systemctl cat otelcol-contrib | grep -A3 ExecStart
# Show any env vars that feed config expansion
systemctl show otelcol-contrib -p Environment
Validate the merged result rather than each file alone; validation reproduces the merge and surfaces the offending key path:
otelcol-contrib validate \
--config /etc/otelcol-contrib/base.yaml \
--config /etc/otelcol-contrib/override.yaml
Compare the type of the named key across sources. Here the base is a map but the override made it a scalar:
# base.yaml — processors is a MAP of component configs
processors:
batch:
send_batch_size: 512
memory_limiter:
check_interval: 1s
limit_mib: 512
# override.yaml — WRONG: this replaces the whole map with a string
processors: batch
Fix the override so it merges as a map, patching only the field you intend to change:
# override.yaml — CORRECT: still a map, so it deep-merges
processors:
batch:
send_batch_size: 1024
If the culprit is env expansion, confirm what the variable resolves to before the Collector reads it:
echo "$PROCESSORS" # if this prints a bare word, it will collapse the block
journalctl -u otelcol-contrib --since '10 min ago' | grep -i 'cannot merge'
Example Root Cause Analysis
A team ran the Collector with a shared base.yaml plus a per-environment override.yaml injected by a config-map. Staging worked; production failed at boot with cannot merge config: '[processors]' expected a map, got 'string'. The base defined processors: as a map with batch and memory_limiter. The production override had been hand-edited to processors: batch — an attempt to “select” the batch processor — which turned the whole key into a scalar. Because production layered that override on top of the base, the merge saw a map on one side and a string on the other and aborted.
The fix had two parts. First, the override was rewritten to keep processors: as a map and only patch the field that actually differed (send_batch_size: 1024), so the deep-merge preserved the base structure. Second, a CI step was added that runs otelcol-contrib validate against the fully merged config for each environment, so a type-shape mismatch fails the pipeline instead of the pod. Production started cleanly and the class of error was caught before deploy thereafter.
Prevention Best Practices
- Keep every override the same YAML type as the key it patches — a map stays a map, a list stays a list.
- Validate the merged configuration (
otelcol-contrib validatewith all--configsources) in CI, not just individual files. - Prefer patching leaf fields in overrides rather than redefining parent blocks like
processors:orexporters:. - Quote and sanity-check any env var feeding
${...}expansion so it never collapses a block to a scalar. - Pin
--configordering explicitly and document which source is the base and which are overrides. - Lint YAML for indentation so a nested key never drifts into a sibling scalar during merge.
Quick Command Reference
# Show every --config source the unit merges, in order
systemctl cat otelcol-contrib | grep -A3 ExecStart
# Validate the merged result and reveal the offending key path
otelcol-contrib validate --config base.yaml --config override.yaml
# Check what an expansion variable resolves to before startup
echo "$PROCESSORS"
# Watch the merge failure at boot
journalctl -u otelcol-contrib --since '10 min ago' | grep -i 'cannot merge'
Conclusion
cannot merge config is a type-shape conflict: two configuration sources hand the Collector different YAML types for the same key, so the deep-merge has nothing consistent to build. Find the key path in the message, compare that key across your base and override sources, and make the override match the expected type — usually a map that patches a single field rather than a scalar that replaces a block. Validating the merged configuration in CI turns this startup crash into a caught pull-request error.
Related
- OpenTelemetry Error Guide: ‘failed to parse config’
- OpenTelemetry Error Guide: ‘references exporter which is not configured’
- OpenTelemetry Error Guide: ‘address already in use’
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.