Skip to content

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:

  1. Norm/documentation only — always apply a config change in DDEV and run drush config:export rather 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).
  2. CI check on the PR — spin up DDEV (or an equivalent throwaway Drupal boot) in CI, run cim then config:status, fail the PR if anything is Different. Would have caught this exact case pre-merge. Adds CI time/complexity; needs a DB/bootstrap fixture.
  3. Pre-commit/local hook — a lighter-weight local check (e.g. lint that flags config YAML diffs not immediately followed by a config:export in the same session) — weaker guarantee than #2 but no CI infrastructure needed.
  4. 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.