Skip to content

fix(release): declare the staging environment so the model guard can see its key - #1144

Merged
frankbria merged 1 commit into
mainfrom
fix/release-guard-environment
Aug 11, 2026
Merged

fix(release): declare the staging environment so the model guard can see its key#1144
frankbria merged 1 commit into
mainfrom
fix/release-guard-environment

Conversation

@frankbria

Copy link
Copy Markdown
Owner

What happened

The v0.9.2 release failed at the model-default guard. Cause: a one-line
omission in my #1112 change.

ANTHROPIC_API_KEY lives in the staging GitHub environment. Environment
secrets reach only jobs that declare that environment, and release.yml's
build job declared none — so it resolved empty, MODEL_GUARD_REQUIRE_LIVE=1
tripped, and the build refused.

lifecycle.yml:33 and engine-smoke.yml:122 both already do
environment: staging for this exact key. I did not follow the existing
convention.

The guard behaved correctly. publish-pypi and github-release were
skipped, nothing reached PyPI (still 0.9.1). It refused to publish unverified —
just for the wrong reason.

The scanner, and what it found

actionlint cannot catch this: it has no way to know which environment a secret
lives 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 production
environment holds zero secrets, while deploy.yml's deploy-production
references ten (SSH_KEY, USER, PROJECT_PATH, AUTH_SECRET,
ANTHROPIC_API_KEY, …).

$ gh api repos/frankbria/codeframe/environments/production/secrets --jq .total_count
0
$ gh api repos/frankbria/codeframe/environments/staging/secrets --jq .total_count
20

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 .env containing an empty AUTH_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.2 currently
points at 60c06c5f, which does not.

…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.
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @frankbria's task in 3m 46s —— View job


Precision review (logic / security / data-loss defects only)

  • Read changed files + surrounding context (release.yml, test file, peer workflows)
  • Analyze scanner logic for concrete failure scenarios
  • Post inline comments for any confirmed defect — none found
  • Post summary

✅ 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:

  • The fix is correct and load-bearing. release.yml:build references secrets.ANTHROPIC_API_KEY at L58 and now declares environment: staging at L30. Removing the line fails both new tests, as the author claims.
  • The scanner is sound for the current repo. Hand-traced all 5 secrets.ANTHROPIC_API_KEY references → 4 declare environment: staging (string form in release.yml/engine-smoke.yml/lifecycle.yml; dict form {name: staging} in deploy.yml:deploy-staging, correctly unwrapped by declared.get("name")) and the 5th (deploy.yml:deploy-production) is the KNOWN_GAPS exemption. offenders == []; the test passes as shipped.
  • No false-positive/negative traps in the matching. secrets.ANTHROPIC_API_KEY has no internal spaces, so yaml.safe_dump's 80-col wrapping can't split the token; safe_load strips comments, so a # mention can't trip a false positive; string and mapping environment: forms are both handled; KNOWN_GAPS short-circuits before the comparison.
  • Glob *.yml is complete today — all 9 workflows use .yml, none .yaml.

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 staging GitHub environment has a deployment-branch policy, it could still block a tag-triggered run of build (on: push: tags: v*). That's a control-plane setting outside the repo, not a code defect — worth the manual confirmation the general review suggested before re-tagging v0.9.2.

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review

Small, well-scoped fix that matches the existing convention (lifecycle.yml:33, engine-smoke.yml:122 both already use environment: staging for ANTHROPIC_API_KEY). The root-cause write-up in the PR description is clear and the fix is exactly what it says: add the missing environment: staging to release.yml's build job so the environment-scoped secret actually resolves.

Test coverage — good instinct to add a regression test for the class of bug rather than just the one line. TestEnvironmentSecretsAreReachable::test_every_job_using_an_environment_secret_declares_it scans all workflow files for secrets.ANTHROPIC_API_KEY usage and checks the owning job declares environment: staging; it correctly handles both the string form (environment: staging) and the mapping form (environment: {name: staging, ...}), so it validates against both release.yml's style and deploy.yml's. The KNOWN_GAPS exemption for deploy-production is handled the right way — pointed at #1143 with a reason, not silenced. test_the_release_build_job_can_see_the_key_it_requires pins the specific guard scenario (MODEL_GUARD_REQUIRE_LIVE + environment: staging together). I traced the logic by hand against the current repo state (couldn't execute uv run pytest in this sandbox — approval-gated) and both new tests should pass: deploy-staging and deploy-production in deploy.yml both declare matching/exempted environments, and no other job references secrets.ANTHROPIC_API_KEY without declaring staging.

One easy follow-up, not blocking: ENVIRONMENT_SECRETS only tracks ANTHROPIC_API_KEY. OPENAI_API_KEY flows through the same environment-gated jobs (engine-smoke.yml's task job, deploy.yml's deploy-staging/deploy-production) — if it's also environment-scoped rather than repo-level, it's exactly the kind of gap this scanner exists to catch, and it currently wouldn't be. Since every current usage happens to already declare the right environment, this isn't live today, but adding it to the map (if confirmed environment-scoped) would close the loophole this PR just opened a category for.

Unverifiable from here: whether the staging GitHub environment has a deployment branch/tag policy that could still block release.yml (which triggers off refs/tags/v*, not a branch push) from exposing the secret even with environment: staging declared. Given lifecycle.yml/engine-smoke.yml already rely on this successfully, it's likely fine, but worth a quick manual confirmation post-merge before re-tagging v0.9.2, since that's the whole point of this fix.

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.

@frankbria
frankbria merged commit d78281c into main Aug 11, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant