fix(release): declare the staging environment so the model guard can see its key - #1144
Conversation
…see its key The v0.9.2 release failed at the model-default guard, and the cause was a one-line omission in my #1112 change. ANTHROPIC_API_KEY lives in the `staging` GitHub environment. Environment secrets are exposed only to jobs that declare that environment, and release.yml's build job declared none — so the secret resolved empty, MODEL_GUARD_REQUIRE_LIVE tripped, and the build refused. lifecycle.yml:33 and engine-smoke.yml:122 both already use `environment: staging` for exactly this key; I simply did not follow the existing convention. The guard behaved correctly throughout: publish-pypi and github-release were skipped and nothing reached PyPI, which is the outcome it exists to produce. It just refused for the wrong reason. Also adds a scanner so this cannot recur: any job referencing a secret that lives in an environment must declare that environment. actionlint cannot catch this — it has no way to know where a secret lives. The scanner immediately found a second, pre-existing defect: the `production` environment holds ZERO secrets while deploy.yml's deploy-production references ten of them (SSH_KEY, USER, PROJECT_PATH, AUTH_SECRET, ANTHROPIC_API_KEY...). That path has never been able to work; it fails on SSH auth rather than deploying something broken, but with SSH alone populated it would write a remote .env with an empty AUTH_SECRET — the JWT signing key. Filed as #1143 (operator decision: populate, delete, or guard) and exempted here with a pointer rather than the check being weakened.
|
Claude finished @frankbria's task in 3m 46s —— View job Precision review (logic / security / data-loss defects only)
✅ GLM review: no defects found. Scope was concrete defects only (logic errors, security, data-loss, races, type mismatches) — style/test-coverage is covered by the general review, so not duplicated here. What I verified by hand against the checked-out repo:
One item I deliberately did not file as a defect, because it's not confirmable from code and is already raised by the general review: if the |
ReviewSmall, well-scoped fix that matches the existing convention ( Test coverage — good instinct to add a regression test for the class of bug rather than just the one line. One easy follow-up, not blocking: Unverifiable from here: whether the No security concerns — this only affects which job can read an already-scoped secret, and the change narrows exposure correctly rather than widening it. No production code paths touched. |
What happened
The
v0.9.2release failed at the model-default guard. Cause: a one-lineomission in my #1112 change.
ANTHROPIC_API_KEYlives in thestagingGitHub environment. Environmentsecrets reach only jobs that declare that environment, and
release.yml'sbuildjob declared none — so it resolved empty,MODEL_GUARD_REQUIRE_LIVE=1tripped, and the build refused.
lifecycle.yml:33andengine-smoke.yml:122both already doenvironment: stagingfor this exact key. I did not follow the existingconvention.
The guard behaved correctly.
publish-pypiandgithub-releasewereskipped, nothing reached PyPI (still 0.9.1). It refused to publish unverified —
just for the wrong reason.
The scanner, and what it found
actionlintcannot catch this: it has no way to know which environment a secretlives in. So this adds a test asserting that any job referencing an
environment-scoped secret declares that environment. Verified non-tautological —
removing the fix fails both new tests.
It immediately found a second, pre-existing defect: the
productionenvironment holds zero secrets, while
deploy.yml'sdeploy-productionreferences ten (
SSH_KEY,USER,PROJECT_PATH,AUTH_SECRET,ANTHROPIC_API_KEY, …).That deploy path has never worked. It fails on SSH auth rather than deploying
something broken — but with the SSH secrets alone populated it would write a
remote
.envcontaining an emptyAUTH_SECRET, the JWT signing key. Filed as#1143 (operator decision: populate, delete, or guard) and exempted here with
a pointer, not silenced.
After this merges
The tag needs moving to a commit that contains the fix —
v0.9.2currentlypoints at
60c06c5f, which does not.