e2e: add direct-Anthropic LLM-key path alongside MiniMax + OpenAI

Adds a third secrets-injection branch in test_staging_full_saas.sh
behind a new E2E_ANTHROPIC_API_KEY env var, wired into all three
auto-running E2E workflows (canary-staging, e2e-staging-saas,
continuous-synth-e2e) via a new MOLECULE_STAGING_ANTHROPIC_API_KEY
repo secret slot.

Operator motivation: after #2578 (the staging OpenAI key went over
quota and stayed dead 36+ hours) we shipped #2710 to migrate the
canary + full-lifecycle E2E to claude-code+MiniMax. Discovered post-
merge that MOLECULE_STAGING_MINIMAX_API_KEY had never been set after
the synth-E2E migration on 2026-05-03 either — synth has been red the
whole time, not just OpenAI quota.

Setting up a MiniMax billing account from scratch is non-trivial
(needs platform-specific signup, KYC, top-up). Operators who already
have an Anthropic API key for their own Claude Code session can now
just set MOLECULE_STAGING_ANTHROPIC_API_KEY and have all three
auto-running E2E gates green within one cron firing.

Priority chain in test_staging_full_saas.sh (first non-empty wins):
  1. E2E_MINIMAX_API_KEY      → MiniMax (cheapest)
  2. E2E_ANTHROPIC_API_KEY    → direct Anthropic (cheaper than gpt-4o,
                                lower setup friction than MiniMax)
  3. E2E_OPENAI_API_KEY       → langgraph/hermes paths

Verify-key case-statement in all three workflows accepts EITHER
MiniMax OR Anthropic for runtime=claude-code; error message names
both options so operators know they don't have to register a MiniMax
account if they already have an Anthropic key.

Pinned to runtime=claude-code — hermes/langgraph use OpenAI-shaped
envs and won't honour ANTHROPIC_API_KEY without further wiring.

After this lands + secret is set, the dispatched canary verifies the
new path:
  gh workflow run canary-staging.yml --repo Molecule-AI/molecule-core --ref staging
This commit is contained in:
Hongming Wang 2026-05-04 00:51:14 -07:00
parent 5596cb52ef
commit 98f883cb99
4 changed files with 87 additions and 12 deletions

View File

@ -63,6 +63,11 @@ jobs:
# full_saas.sh branches SECRETS_JSON on which key is present —
# MiniMax wins when set.
E2E_MINIMAX_API_KEY: ${{ secrets.MOLECULE_STAGING_MINIMAX_API_KEY }}
# Direct-Anthropic alternative for operators who don't want to
# set up a MiniMax account (priority below MiniMax — first
# non-empty wins in test_staging_full_saas.sh's secrets-injection
# block). See #2578 PR comment for the rationale.
E2E_ANTHROPIC_API_KEY: ${{ secrets.MOLECULE_STAGING_ANTHROPIC_API_KEY }}
# OpenAI fallback — kept wired so an operator-dispatched run with
# E2E_RUNTIME=hermes overridden via workflow_dispatch can still
# exercise the OpenAI path without re-editing the workflow.
@ -97,8 +102,20 @@ jobs:
# missing" message at the top.
case "${E2E_RUNTIME}" in
claude-code)
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY"
required_secret_value="${E2E_MINIMAX_API_KEY:-}"
# Either MiniMax OR direct-Anthropic works — first
# non-empty wins in the test script's secrets-injection
# priority chain. Operators only need to set ONE of these
# secrets; we don't force a choice between them.
if [ -n "${E2E_MINIMAX_API_KEY:-}" ]; then
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY"
required_secret_value="${E2E_MINIMAX_API_KEY}"
elif [ -n "${E2E_ANTHROPIC_API_KEY:-}" ]; then
required_secret_name="MOLECULE_STAGING_ANTHROPIC_API_KEY"
required_secret_value="${E2E_ANTHROPIC_API_KEY}"
else
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY or MOLECULE_STAGING_ANTHROPIC_API_KEY"
required_secret_value=""
fi
;;
langgraph|hermes)
required_secret_name="MOLECULE_STAGING_OPENAI_KEY"

View File

@ -119,6 +119,11 @@ jobs:
# tests/e2e/test_staging_full_saas.sh branches SECRETS_JSON on
# which key is present — MiniMax wins when set.
E2E_MINIMAX_API_KEY: ${{ secrets.MOLECULE_STAGING_MINIMAX_API_KEY }}
# Direct-Anthropic alternative for operators who don't want to
# set up a MiniMax account (priority below MiniMax — first
# non-empty wins in test_staging_full_saas.sh's secrets-injection
# block). See #2578 PR comment for the rationale.
E2E_ANTHROPIC_API_KEY: ${{ secrets.MOLECULE_STAGING_ANTHROPIC_API_KEY }}
# OpenAI fallback — kept wired so operators can dispatch with
# E2E_RUNTIME=langgraph or =hermes and still have a working
# canary path. The script picks the right blob shape based on
@ -149,13 +154,21 @@ jobs:
exit 1
fi
# LLM-key requirement is per-runtime: claude-code uses MiniMax
# (MOLECULE_STAGING_MINIMAX_API_KEY), langgraph + hermes use
# OpenAI (MOLECULE_STAGING_OPENAI_KEY).
# LLM-key requirement is per-runtime: claude-code accepts
# EITHER MiniMax OR direct-Anthropic (whichever is set first),
# langgraph + hermes use OpenAI (MOLECULE_STAGING_OPENAI_KEY).
case "${E2E_RUNTIME}" in
claude-code)
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY"
required_secret_value="${E2E_MINIMAX_API_KEY:-}"
if [ -n "${E2E_MINIMAX_API_KEY:-}" ]; then
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY"
required_secret_value="${E2E_MINIMAX_API_KEY}"
elif [ -n "${E2E_ANTHROPIC_API_KEY:-}" ]; then
required_secret_name="MOLECULE_STAGING_ANTHROPIC_API_KEY"
required_secret_value="${E2E_ANTHROPIC_API_KEY}"
else
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY or MOLECULE_STAGING_ANTHROPIC_API_KEY"
required_secret_value=""
fi
;;
langgraph|hermes)
required_secret_name="MOLECULE_STAGING_OPENAI_KEY"

View File

@ -93,6 +93,11 @@ jobs:
# OpenAI quota collapse no longer wedges the gate. Mirrors the
# canary-staging.yml + continuous-synth-e2e.yml migrations.
E2E_MINIMAX_API_KEY: ${{ secrets.MOLECULE_STAGING_MINIMAX_API_KEY }}
# Direct-Anthropic alternative for operators who don't want to
# set up a MiniMax account (priority below MiniMax — first
# non-empty wins in test_staging_full_saas.sh's secrets-injection
# block). See #2578 PR comment for the rationale.
E2E_ANTHROPIC_API_KEY: ${{ secrets.MOLECULE_STAGING_ANTHROPIC_API_KEY }}
# OpenAI fallback — kept wired so an operator-dispatched run with
# E2E_RUNTIME=hermes or =langgraph via workflow_dispatch can still
# exercise the OpenAI path.
@ -128,8 +133,19 @@ jobs:
# clean "secret missing" message at the top.
case "${E2E_RUNTIME}" in
claude-code)
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY"
required_secret_value="${E2E_MINIMAX_API_KEY:-}"
# Either MiniMax OR direct-Anthropic works — first
# non-empty wins in the test script's secrets-injection
# priority chain.
if [ -n "${E2E_MINIMAX_API_KEY:-}" ]; then
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY"
required_secret_value="${E2E_MINIMAX_API_KEY}"
elif [ -n "${E2E_ANTHROPIC_API_KEY:-}" ]; then
required_secret_name="MOLECULE_STAGING_ANTHROPIC_API_KEY"
required_secret_value="${E2E_ANTHROPIC_API_KEY}"
else
required_secret_name="MOLECULE_STAGING_MINIMAX_API_KEY or MOLECULE_STAGING_ANTHROPIC_API_KEY"
required_secret_value=""
fi
;;
langgraph|hermes)
required_secret_name="MOLECULE_STAGING_OPENAI_KEY"

View File

@ -321,8 +321,9 @@ tenant_call() {
# ─── 5. Provision parent workspace ─────────────────────────────────────
# Inject the LLM provider key so the runtime can authenticate at boot.
# Branch by which secret is set so the script supports both paths
# without forcing every dispatch to ship both keys:
# Branch by which secret is set so the script supports multiple paths
# without forcing every dispatch to ship them all. Priority order
# matters — first non-empty wins:
#
# E2E_MINIMAX_API_KEY → claude-code MiniMax path. Cheapest, default
# for the cron canary post-2026-05-03. Routes via the claude-code
@ -334,6 +335,15 @@ tenant_call() {
# collisions when a user runs MiniMax + Z.ai workspaces side-by-
# side).
#
# E2E_ANTHROPIC_API_KEY → claude-code direct-Anthropic path (added
# 2026-05-04 after #2578 left the operator with an awkward choice
# between paying OpenAI's billing top-up and registering a new
# MiniMax account). Lower friction than MiniMax for operators
# who already have an Anthropic API key for their own Claude
# Code session. Pricier per-token than MiniMax but billing is
# still independent of MOLECULE_STAGING_OPENAI_KEY. Pinned to the
# claude-code runtime — hermes/langgraph use OpenAI-shaped envs.
#
# E2E_OPENAI_API_KEY → langgraph + hermes paths. Kept as fallback
# for operator dispatches that explicitly want to exercise the
# OpenAI path. The HERMES_* fields pin hermes-agent's bridge to
@ -341,7 +351,7 @@ tenant_call() {
# resolves openai/* → openrouter.ai and 401s). MODEL_PROVIDER
# follows workspace/config.py:258's 'provider:model' format.
#
# Both empty → '{}' (workspace will fail at first turn with an
# All empty → '{}' (workspace will fail at first turn with an
# expected, actionable auth error rather than masking the test).
SECRETS_JSON='{}'
if [ -n "${E2E_MINIMAX_API_KEY:-}" ]; then
@ -352,6 +362,25 @@ print(json.dumps({
'MINIMAX_API_KEY': k,
}))
")
elif [ -n "${E2E_ANTHROPIC_API_KEY:-}" ]; then
# Direct Anthropic path — claude-code adapter reads ANTHROPIC_API_KEY
# natively when ANTHROPIC_BASE_URL is unset. Useful for operators
# who already have an Anthropic API key (e.g. for their own Claude
# Code session) and want to avoid setting up a separate MiniMax
# account just for E2E. Pricier per-token than MiniMax but billing
# is still independent of MOLECULE_STAGING_OPENAI_KEY, so an OpenAI
# quota collapse doesn't wedge this path. Pinned to the claude-code
# runtime: hermes/langgraph use OpenAI-shaped envs and won't honour
# ANTHROPIC_API_KEY without further wiring (out of scope for this
# branch; if you need a hermes/Anthropic path, dispatch with
# E2E_RUNTIME=hermes + E2E_OPENAI_API_KEY pointing at a working key).
SECRETS_JSON=$(python3 -c "
import json, os
k = os.environ['E2E_ANTHROPIC_API_KEY']
print(json.dumps({
'ANTHROPIC_API_KEY': k,
}))
")
elif [ -n "${E2E_OPENAI_API_KEY:-}" ]; then
SECRETS_JSON=$(python3 -c "
import json, os