A local dev server (any repo using the secrets/<name>.env.sops → .env.local
pattern) throws auth errors against a service whose credential was recently rotated
in SOPS — e.g. MongoDB MongoServerError: Authentication failed., a Supabase 401,
or an n8n webhook signature mismatch. The credential value in the workstation’s
.env.local no longer matches the live server-side value because SOPS was updated
(rotation, exposure incident, new key added) but .env.local was never regenerated.
First observed: 2026-08-04, et-operational-platform, PINBOX24_MONGODB_URI —
rotated 2026-08-02 via issue #5079
(credential exposed in a worker chat transcript, see #5078). The rotation and its
live-server “Phase B” write both completed and the issue closed same day, but this
workstation’s .env.local was generated before that and never re-synced — the dev
session had no way to know a rotation had happened.
Confirm
Identify which SOPS file backs the failing credential (see the project’s
CLAUDE.md → Secrets section for the secrets/<name>.env.sops → key map).
Check whether that file has a commit newer than .env.local’s mtime:
# SOPS file's last relevant commitcd C:\code_2026\p24-infragit log -1 --format="%h %ad" --date=iso -- secrets/<name>.env.sops# .env.local's last write time(Get-Item "C:\code_2026\<repo>\.env.local").LastWriteTime
If the commit date is newer than the file’s LastWriteTime, .env.local is stale.
Confirm the specific credential is actually the cause (don’t guess): reproduce the
failure and match the error to the connection using that credential — e.g. a Mongo
driver “Authentication failed” implicates *_MONGODB_URI, a Supabase 401
implicates the anon/service-role key, an n8n webhook 401 implicates the shared
HMAC secret.
Fix
Regenerate .env.local from the current SOPS value and restart the dev process —
module-level process.env.X reads (e.g. a MongoDB client singleton built at import
time) do not pick up a live env reload; the Node process must restart.
powershell scripts/decrypt-et-op-env.ps1 # project-specific decrypt script — see repo CLAUDE.md
Then kill and restart the dev server (/restart-dev in et-operational-platform,
or the project’s equivalent). If a port’s listener PID has already detached from the
harness’s tracked background task (task shows “completed” but the port is still
listening), find and kill it directly:
If the regenerated .env.local still fails auth, the live server-side credential
may not match SOPS yet (an in-progress rotation’s “Phase B” not applied) — check
the rotation-log / the relevant [SECRET-REQUEST] issue for phase status before
assuming the local fix is wrong.
If there’s no local p24-infra clone, or sops/age key access is unavailable,
escalate via /request-sm (ad-hoc) or a [SECRET-REQUEST] issue per
docs/playbooks/secret-manager-request.md.
Prevention
et-operational-platform now runs a predev npm hook
(scripts/check-env-staleness.js) that compares .env.local’s mtime against the
last commit touching secrets/et-operational-platform.env.sops in the local
p24-infra clone, and prints a non-blocking warning + fix command when stale. It
fails open (silently skips) if the p24-infra clone, git, or .env.local aren’t
present — safe in CI and on other machines.
To roll the same pattern out to another project:
Copy scripts/check-env-staleness.js, updating SOPS_RELATIVE_PATH to that
project’s secrets/<name>.env.sops.
Add "predev": "node scripts/check-env-staleness.js" to package.json scripts.
Note the mechanism in that repo’s CLAUDE.md Secrets section.
This is best-effort, not a guarantee — it only compares against the local clone’s
git history, so a workstation that hasn’t git pulled p24-infra recently won’t see
the newest rotation either. It catches the common case (dev forgot to re-sync after
being told a rotation landed) rather than every possible staleness path.