Hand-edited config YAML drifts from what Drupal actually computes — the deploy guard catches it, but only at deploy time
Area: deployment / CMI / config sync / DX
Raised during: Session 2026-09-02, fixing the uva-mandala-drupal-codepipeline failure caused by PR #177
Jira: (add when available)
Priority: Medium — not blocking (worked around this time), but will recur for anyone who hand-edits a config YAML instead of exporting it from a live site
Awaiting a team decision
This item is blocked on a group conversation about workflow, not on implementation — do not start it solo. Put it on the next group-meeting agenda.
What happened
PR #177 (KMaps popover) hand-edited core.entity_view_display.node.shanti_image.grid_details.yml
to change two formatters and un-hide a field, rather than applying the change in a live Drupal
site and running drush config:export. The edit was semantically correct, but Drupal reorders a
view display's content mapping alphabetically by field machine name when it saves the entity,
and the deploy pipeline's post-cim drift guard (deploy_backend.yml, added deliberately per
deploy-never-imports-config-sync.md to close out exactly
this class of silent-drift failure) compares config by exact value, not semantic equivalence — so
the hand-typed key order left the site's active config permanently "Different" from the sync
directory, failing the deploy stage on every subsequent run until fixed. See the fix in
PR #179, and the session log for the full
trace.
The guard did exactly its job — it caught real drift before it could reach a "config exists in git
but was never actually verified live" state. The problem is when it caught it: at deploy time,
on main, after merge, blocking the pipeline for everyone until someone notices and fixes it —
not at PR review time, when the author (or Claude) still has full context.
The open question
How should hand-edited (or Claude-authored) config YAML be validated before it reaches main,
rather than relying on the deploy-time guard as the only backstop? Candidate approaches, not yet
weighed against each other:
- Norm/documentation only — always apply a config change in DDEV and run
drush config:exportrather than hand-editing YAML; treat hand-edited config YAML as a smell in review. Lowest effort, weakest enforcement (relies on remembering, including by Claude across sessions). - CI check on the PR — spin up DDEV (or an equivalent throwaway Drupal boot) in CI, run
cimthenconfig:status, fail the PR if anything isDifferent. Would have caught this exact case pre-merge. Adds CI time/complexity; needs a DB/bootstrap fixture. - Pre-commit/local hook — a lighter-weight local check (e.g. lint that flags config YAML
diffs not immediately followed by a
config:exportin the same session) — weaker guarantee than #2 but no CI infrastructure needed. - Leave it as-is — the deploy-time guard already prevents bad config from silently reaching the running site (which was the original, worse failure mode this guard replaced); accept that fixing it costs one blocked deploy each time it happens, given how infrequently config YAML is hand-edited versus generated by the Field UI/normal admin flows.
No option has been picked; this needs the group's input on how much CI investment is worth it for how often this actually recurs.
Update 2026-09-15: recurred, with a real deploy failure this time
Hand-edited core.entity_view_display.node.video.default.yml (AV9, PR #209) to add a
field_video component, inserting it before field_kmap_terms in the content: mapping.
Same root cause as PR #177: Drupal sorts content alphabetically by field name on save;
field_video belongs after field_subject. This time the guard didn't just flag a
pre-existing drift found later — it failed dev-0's actual deploy pipeline (Deploy stage:
FAILED, "verify configuration import left no drift"), because the broken commit had already
merged and the webhook auto-deployed it.
This is the second real recurrence of exactly this failure mode, and notably: it happened
immediately after documenting a related-but-distinct config/sync risk
([[feedback-config-export-not-scoped]]) in the very same session — general awareness that
"config/sync is fragile" did not prevent the specific mistake. Fixed by reading the actual
active config back out of Drupal after import (not another hand-edit) and writing its exact
canonical serialization to the sync file — see [[feedback-never-hand-edit-config-structure]]
for the full writeup and the concrete "how to apply" rule (never hand-edit config structure,
only scalar values; always verify with drush config:status before pushing).
Given this has now caused two real deploy incidents, option 2 (a CI check running cim +
config:status on every PR touching config/sync) is worth re-raising with the group with
more urgency than "no option chosen yet" implies — it would have caught both incidents before
merge, not just after.
Related
- Deploy never imports config/sync (the guard's origin)
- config:export not scoped, strips comments — the sibling risk found the day before this recurrence