Use when reviewing configuration concerns: env-var layering, typed config objects, startup validation, secret/non-secret separation, schema discoverability, environment-dependent defaults, twelve-factor compliance, magic numbers
How this skill is triggered — by the user, by Claude, or both
Slash command
/software-leverage-points:configurationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Configuration is the seam between the same code artifact and every environment it deploys into. Done well, configuration is a single typed surface, validated at startup, with a documented schema and explicit precedence. Done poorly, configuration is a scatter of `os.environ` reads, an unsafe default that silently ships to production, and a tribal-knowledge tax paid by every new contributor.
Configuration is the seam between the same code artifact and every environment it deploys into. Done well, configuration is a single typed surface, validated at startup, with a documented schema and explicit precedence. Done poorly, configuration is a scatter of os.environ reads, an unsafe default that silently ships to production, and a tribal-knowledge tax paid by every new contributor.
Core principle: Configuration is one explicit, validated, discoverable surface. The application declares it; the environment binds it; startup refuses to proceed if the binding is incomplete.
A single typed config object is the canonical surface for every variable the project reads, and it is the source every downstream tool reads from. Direct env-var reads (os.environ["X"] or equivalent) scattered through business logic and request handlers defeat type-checking, default handling, and enumeration. The registry carries name, type, default, description, required-ness, and any cross-cutting metadata (which secret store backs it, which scopes consume it). Tools that generate .env.example, sync secrets, run doctor checks, and emit infra params all read from the same object: nothing else in the project is authoritative on these facts.
A contributor should be able to answer "what configuration does this service need" by reading one file, not by grepping the codebase. Magic strings for env-var names (literal "DATABASE_URL" recurring across modules) are the most common form of duplication this principle eliminates: the registry holds the name once, and every reader references the typed field, not the literal. See typed-config-registry/README.md for Python (Pydantic Settings) and TypeScript registry-object examples.
Cross-reference: the dry skill carries the don't-duplicate-the-value-across-files check that complements the typed-config principle this skill carries.
A service that reads configuration at runtime must ship a discoverable schema. The schema is generated from the typed registry, not hand-maintained: a committed .env.example (or JSON Schema) emitted by a script that introspects the registry stays in sync by construction, and reviewers read one diff to see configuration shape change. Hand-edited example files drift the moment a field is added or renamed, and the drift surfaces as a startup error in someone else's environment a week later.
Local .env files are populated by the same generator, idempotently: missing keys are added (with a placeholder or default), existing values are preserved, and removed keys are flagged rather than deleted. This is the difference between "regenerate overwrites my local edits" (developer hostile) and "regenerate brings my local file forward" (developer friendly). See developer-experience for the broader local-loop story.
This principle scopes to services with a startup phase that precedes request handling. Long-lived services validate every required value before the first request: a misconfigured deploy must refuse to come up, not half-process traffic and crash deep in a handler. CLI tools and scripts may legitimately validate at first use; document the choice.
Late-binding configuration failures are release-blocking surface area pretending to be runtime errors. They corrupt observability and produce partial outages.
Calibrate the severity to the requirement. Required values fail fast: startup aborts with a clear error naming the variable and where to set it. Optional values that are missing log a warning at startup explaining what capability is degraded ("WHISPER_BASE_URL not set; whisper transcription will report disabled-missing-config"), so the operator gets fast feedback without losing the running system. Silent degradation is the worst outcome: the feature is off, no one knows, and discovery happens via a user report. Cross-reference: see developer-experience for the broader fast-feedback stance and logging for how the warning is shaped.
Secrets and tunables live in different stores. Mixing them collapses the trust boundary: a developer changing a non-secret tunable is forced through the secret-handling path, and a debugging tool that dumps non-secret config accidentally dumps secrets. The twelve-factor stance is one rule, two stores: secrets in a managed secret store, non-secrets in the environment or a tracked file.
Multiple valid secret stores exist: centralized secret manager, cloud-provider native, envelope-encrypted files in source. The principle is: pick one and apply it consistently. The red flag is "secrets and non-secrets in the same namespace," not "did not pick a particular vendor." See the security skill for whether secrets exist in source at all.
Production-relevant values have no default: the deploy refuses to start if the override is missing. Defaults belong to development and test, where a forgotten override produces a local error rather than an outage or a security incident. A default of localhost, dev, or a permissive CORS list is fail-open by construction.
Timeouts, retry counts, batch sizes, rate limits, and feature toggles that plausibly change across environments belong in configuration, not as magic numbers in source. The tell is whether the value has changed across environments before, or plausibly might. Configurability has a cost (more surface to validate and document), so the test is "does this knob need to turn at deploy time," not "could this conceivably be tuned."
When two or more configuration sources combine (defaults, files, environment, CLI flags), the order of precedence is documented and applied in one place by the typed config object. Implicit precedence means a contributor changes a value in the obvious place and nothing happens because a different source overrides it silently.
Multiple valid precedence chains exist: defaults-then-file-then-env-then-CLI is conventional; other orderings are defensible. The principle is: state the chain and apply it consistently. The red flag is "no stated precedence," not "did not pick the canonical chain."
Cross-reference: the environments skill carries the per-environment parity stance for the loader itself: dev, staging, and production may bind different sources, but the variable names, shape, and precedence chain stay uniform across them.
Every application has an irreducible amount of complexity; configuration design decides who pays it. When the registry is a wall of required knobs with no defaults, that complexity flows outward to every operator, contributor, and CI runner. When the registry ships sane defaults for the development path and saves the no-default discipline for genuinely environment-specific values (production endpoints, secrets, scaling parameters), the same code is reachable on a fresh clone with just up and locked down at deploy time.
The test for a default is not "could this conceivably differ?" but "does this need to differ to run the project on a teammate's laptop?" Defaults that only make sense for one environment violate principle 5 (no production-relevant defaults). Defaults that absorb the dev path are a force multiplier.
This principle is not "configure less"; it is "configure with hierarchy." Required deploy-time values stay required. Tunables with one obviously-good development value get that value as a default. The cognitive load of running the project converges to the few things that genuinely vary.
Feature toggles for testability, A/B testing, and graceful degradation belong in the same typed registry, not in a parallel system. Two patterns recur, and the second is preferred for stable features:
X_ENABLED=true (opt-in): every feature requires explicit activation. Useful for genuinely experimental work, but expands the configuration surface for every contributor. The unconfigured default is "off," which silently disables behavior that should be on.X_DISABLED=true (opt-out): the feature is on by default; an explicit flag turns it off. Preferred once a feature is stable: the default path runs the system as designed; operators only set values when overriding behavior. Disable-flags also pair naturally with implicit-disable: when the required configuration for a feature is absent, the feature reports "disabled-missing-config" rather than crashing on first use.Cross-reference: see architecture for feature flags as an architectural seam (toggleable code paths) rather than a configuration encoding. The continuous-delivery skill consumes flags as the deploy/release decoupling primitive (gradual rollout, kill-switch abort).
os.environ["X"] or equivalent) scattered through business logic and handlers without a central typed config object.env.example, one for secret sync, one for doctor checks) instead of reading the registry.env.example is hand-maintained and drifts from the registry; new fields appear in code without a corresponding example entry.env instead of overlaying missing keys idempotently.env.example, JSON Schema, or generated reference listing every variable, its type, and whether it is requiredlocalhost, dev, permissive CORS) without a requirement that production override themX_ENABLED=true flag; the default-off shape makes the running system depend on a wall of opt-ins rather than starting up correctly out of the box| Excuse | Reality |
|---|---|
| "It's just one env-var read inline" | Each ad-hoc read forks the configuration surface; a year later there are forty |
| "We'll write the example file later" | Until then, every onboarding starts with a startup-error scavenger hunt |
| "Validation at startup is over-engineering" | The alternative is partial outages where the misconfiguration looks like a request bug |
| "Secrets in the env are fine; nobody can see it" | Logs, debug dumps, and process listings disagree |
| "The default of localhost is harmless" | Until production deploys without the override and silently talks to itself |
| "These constants will never change" | They change at the worst possible moment, and now it's a deploy not a config flip |
| "Precedence is obvious from the code" | Until two sources collide and the loser is wrong silently |
"Hand-editing .env.example is fine; we'll remember to update it" | We won't; the field added in code today ships without an example entry next week |
| "Regenerate is destructive; we'll just keep .env up to date manually" | Manual sync is a tax; idempotent overlay (add missing, preserve existing) makes regenerate safe |
| "Every feature should be opt-in for safety" | Once a feature is stable, opt-in expands the configuration surface for every contributor; opt-out is the smaller default |
| "More knobs is more flexible" | More knobs is more cognitive load; smart defaults preserve flexibility without making operators pay for it |
✅ Settings = TypedConfig(); from settings import settings; settings.db_url
❌ os.environ["DB_URL"] scattered across modules
✅ .env.example committed; every required variable listed with type and example
❌ Required variables documented only in the startup error you hit when you forget one
✅ App refuses to start if DATABASE_URL is missing; clear error names the variable
❌ App starts; first DB-touching request crashes with KeyError deep in a handler
✅ App starts; logs "WHISPER_BASE_URL not set; whisper feature disabled-missing-config"
❌ App starts; whisper feature is silently off; first user report is the only signal
✅ Secrets in a managed store; non-secret tunables in env or a tracked file
❌ API keys and feature flags share .env, with no separation of read scope
✅ No default for PRODUCTION_API_URL; deploy fails closed if missing
❌ Default of "http://localhost:8080" silently ships to prod when override is forgotten
✅ TIMEOUT_SECONDS = settings.timeout_seconds (configurable)
❌ requests.get(url, timeout=30) (magic number; cannot tune without a deploy)
✅ Documented chain: defaults → file → env → CLI; applied in one place
❌ Some values read from env, some from file; precedence varies by import order
✅ .env.example regenerated from typed registry by `just gen-env`; CI fails if drift detected
❌ .env.example hand-edited; new field added in code last week is still missing from the example
✅ `just sync-env` adds missing keys to .env, preserves existing values, prints diff
❌ Regenerate overwrites the developer's local .env; everyone re-enters their secrets
✅ Sensible dev defaults; production-only values have no default and fail closed
❌ Every variable required; fresh clone cannot start without a wiki-tour of secrets
✅ WHISPER_DISABLED=true to opt out; default-on with implicit-disable when config missing
❌ WHISPER_ENABLED=true required for every contributor; default-off silently breaks the happy path
Configuration is the surface where the same code meets every environment. The cost of getting it wrong is paid at deploy time, in incidents, and in the slow tax of every contributor who cannot tell what the service needs to run.
Without disciplined configuration:
With disciplined configuration:
Soft sketch; not a checklist. Where appropriate is shaped by the target's maturity.
.env.example exists; configuration is read in one place even if the typed object is rudimentary. Sensible development defaults so the project runs on a fresh clone. Awareness that the schema will need to be discoverable as the surface grows..env.example generated from the registry rather than hand-edited; idempotent local-overlay tool. Secrets at least separated into their own file even if not yet in a managed store. Stable features adopt the disable-flag shape rather than opt-in..env.example generation, secret sync, doctor checks, and infra param emission; startup validation enforced; secrets in a managed store; precedence chain documented; no production-relevant defaults. Magic numbers reviewed and lifted to config when environment-varying. Feature flags live in the registry.security skill carries the "secrets in source" check; this skill carries the "secrets share a namespace with tunables" check.scripts/config/env.ts. Illustrates principle 1 (single typed registry) and principle 9 (X_DISABLED opt-out flag, implicit-disable when config missing). The registry is the source consumed by sync-secrets, doctor, gen-ssm-params, and resolve-env: nothing else is authoritative on env-var names.packages/syn-shared/src/syn_shared/settings/config.py plus scripts/generate_env_example.py. Illustrates principle 1 (Pydantic Settings as registry with Field(description=...)) and principle 2 (.env.example generated by introspecting the Settings class).These go stale fast; the date is the "as-of." Verify currency before adopting. The principles above outlast specific libraries; if a tool here is no longer maintained, the patterns transfer to its replacement.
python-dotenv for .env loading in development; cattrs for structured deserialization.as const satisfies Record<string, SecretDef> for the registry shape (see typed-config-registry/README.md); zod or io-ts for runtime-validated schemas; convict for layered config; framework-native config (Next.js, NestJS) where the framework provides one.viper or koanf for layered config; envconfig for typed env-var loading.figment, config-rs, or envy for layered/typed config.op:// references resolved at startup. Choice depends on deployment context; consistency within a project matters more than vendor..env.example generated by introspecting the typed registry (see typed-config-registry/README.md); idempotent overlay tool that adds missing keys to a developer's .env without overwriting existing values; JSON Schema emitted from the same registry; --print-config CLI flag that emits the resolved configuration with secrets redacted.X_DISABLED) and per-feature requiredEnvVars metadata, is sufficient for most projects. Dedicated platforms (LaunchDarkly, Unleash, OpenFeature) become valuable once flags need runtime targeting, percentage rollouts, or audit trails.This skill is maintained at: https://github.com/syntropic137/software-leverage-points/blob/main/skills/configuration/SKILL.md
To improve it, edit the file directly and follow the chassis discipline in maintaining-software-leverage-points: regenerate catalogs, run just qa, then commit.
npx claudepluginhub syntropic137/software-leverage-points --plugin software-leverage-pointsProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.