skills/devops/config-consistency-checker/SKILL.md
Detects inconsistencies across configuration files, environments, and deployment manifests — missing keys, drifted values, type mismatches. Use when debugging why staging behaves differently from production, before a deploy to catch config drift, or when auditing multi-environment configs.
npx skillsauth add santosomar/general-secure-coding-agent-skills config-consistency-checkerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
"Works in staging, broken in prod" is almost always a config diff. Find it before deploy, not after.
| Axis | Compare | Looking for |
| --------------------------------- | -------------------------------------------------------- | ------------------------------------------------- |
| Across environments | staging.yaml ↔ production.yaml | Keys present in one, missing in other |
| Config file ↔ code | Config keys ↔ config.get(...) / os.environ[...] calls | Dead keys (in config, never read) and missing keys (read, never defined) |
| Config file ↔ deployment | App config ↔ K8s ConfigMap/Deployment env | Config expects FOO; deployment sets FOO_URL |
| Config file ↔ schema/defaults | Loaded values ↔ type annotations / validators | "30" where the code expects 30 |
| Across replicas (runtime) | Live config on pod A ↔ pod B | Partial rollout, stale ConfigMap mount |
dotted.key.path → value pairs.staging_keys − production_keys → "missing in prod" (usually a bug)
production_keys − staging_keys → "missing in staging" (usually a bug, unless it's a prod-only integration)
staging[k] ≠ production[k] → "differs" — intentional or drift?
Not every difference is a bug. The signal is whether the difference is explicable:
| Difference class | Intentional? |
| ----------------------------------- | --------------------------------------------------------- |
| Database host differs | Intentional — they're different databases |
| Pool size differs | Maybe — prod should be ≥ staging; if prod is smaller, bug |
| Log level differs | Intentional (prod=INFO, dev=DEBUG). If prod=DEBUG, bug |
| Feature flag differs | Depends entirely on the flag — ask |
| Key exists in one, not the other | Almost always drift — someone forgot to add it to both |
| Typo'd key (close string distance) | Always drift — databse_host is reading the default |
Files: config/staging.yaml, config/production.yaml
Flattened:
staging:
db.host = staging-db.internal
db.pool_size = 20
cache.ttl_seconds = 300
feature.new_checkout = true
log.level = debug
production:
db.host = prod-db.internal
db.pool_size = 10
cache.ttl_seconds = 300
feature.new_checkout = true
log.level = info
sentry.dsn = https://...
Diff:
| Key | Staging | Prod | Verdict |
| ----------------------- | ------- | ------------ | ---------------------------------------- |
| db.host | differs | differs | ✅ Intentional — different databases |
| db.pool_size | 20 | 10 | ⚠️ Prod pool smaller than staging — probably wrong |
| cache.ttl_seconds | 300 | 300 | ✅ Match |
| feature.new_checkout | true | true | ✅ Match |
| log.level | debug | info | ✅ Intentional — expected split |
| sentry.dsn | — | set | ⚠️ Missing in staging — errors from staging go nowhere |
Two flags, both worth raising.
Grep the codebase for config reads:
rg 'config\[|config\.get|os\.environ\[|getenv\(' --type py
Extract the key names. Compare against what's actually in the config files:
password: *** ≠ password: *** by string comparison but they're both set — that's what matters.[a, b] vs [b, a] — depends on semantics. A list of allowed origins is a set (order irrelevant); a list of middleware is a sequence (order critical). You have to know which.helm template / kustomize build first.config.get("foo", sensible_default). But flag it — implicit defaults drift silently.database_url in config and DATABASE_URI in code — Levenshtein distance 2, probably the bug.## Missing keys
<key> — in <envA>, missing from <envB> [<likely intentional | likely drift>]
## Suspicious value diffs
<key> <envA>=<val> <envB>=<val> — <why suspicious>
## Dead config (in file, never read by code)
<key>
## Unbacked reads (in code, never in config — will default or crash)
<file>:<line> reads <key> — default: <value | NONE — will crash>
## Clean
<N> keys match across all environments
development
Extracts human-readable pseudocode from a verified formal artifact (Dafny, Lean, TLA+) while preserving the verified properties as annotations, so the proof-carrying logic can be reimplemented in a production language. Use when porting verified code to an unverified target, when documenting what a formal spec actually does, or when handing a verified algorithm to an implementer.
development
Translates natural-language or pseudocode descriptions of concurrent and distributed systems into TLA+ specifications ready for the TLC model checker. Identifies state variables, actions, type invariants, safety properties, and liveness properties from the description. Use when formalizing a protocol, when the user describes a distributed algorithm to verify, when designing a consensus or locking scheme, or when starting formal verification of a concurrent system.
testing
Reduces a TLA+ model so TLC can actually check it — shrinks constants, adds state constraints, abstracts data, or applies symmetry — when the state space is too large to enumerate. Use when TLC runs out of memory, when checking takes hours, or when a spec works at N=2 and you need confidence at larger scale.
development
TLA+-specific instance of model-guided repair — reads a TLC error trace, identifies the enabling condition that should have been false, strengthens the corresponding action, and maps the fix to source code. Use when TLC reports an invariant violation or deadlock and you have the code-to-TLA+ mapping from extraction.