fix(ci): require billable Anthropic release key

This commit is contained in:
Vincent Koc 2026-06-16 20:02:03 +08:00
parent 65805e519d
commit ed16f8fcf0
No known key found for this signature in database
12 changed files with 61 additions and 84 deletions

View file

@ -16,15 +16,10 @@ Use this with `$release-openclaw-maintainer` and `$openclaw-testing` when a rele
- Watch one parent run plus compact child summaries. Avoid broad `gh run view` polling loops; REST quota is easy to burn.
- Fetch logs only for failed or currently-blocking jobs. If quota is low, stop polling and wait for reset.
- Treat live-provider flakes separately from code failures: prove key validity, provider HTTP status, retry evidence, and exact failing lane before editing code.
- Anthropic release lanes support both API keys and OAuth. When API keys are
exhausted but a maintainer-owned OAuth token passes a live Anthropic probe,
set `ANTHROPIC_OAUTH_TOKEN` for provider/runtime lanes and
refreshable `OPENCLAW_CLAUDE_CREDENTIALS_JSON` or
`CLAUDE_CODE_OAUTH_TOKEN` for Claude CLI subscription lanes before rerunning
the matrix. Revalidate short-lived OAuth immediately before dispatch. Never
keep retrying a known exhausted API key. Live-cache validation must prefer
the proven OAuth token instead of leaving an exhausted API key first in the
runtime key pool.
- A model-list response proves authentication, not billing or inference
entitlement. Mandatory live providers must pass a real completion probe
before release dispatch. Fix the credential first; do not add an alternate
auth path merely to bypass a failed release credential.
- Full Release Validation parent monitors fail fast: once a required child job
fails, the parent cancels the remaining child matrix and prints the failed
job summary. Inspect that first red job instead of waiting for unrelated
@ -45,8 +40,8 @@ git rev-parse HEAD
preflight. Inject those exact targeted keys first, then run the verifier; use
ambient env only when it was already intentionally injected for this release.
The script prints only provider status and HTTP class, never tokens.
For Anthropic it prefers `ANTHROPIC_OAUTH_TOKEN` and validates it with bearer
OAuth headers when present; otherwise it checks API-key-shaped credentials.
The Anthropic check performs a tiny message completion so exhausted or
non-billable credentials fail before the expensive release matrix.
## Dispatch
@ -124,11 +119,10 @@ Stop watchers before ending the turn or switching strategy.
--jq '.jobs[] | select(.conclusion=="failure" or .conclusion=="timed_out" or .conclusion=="cancelled") | [.databaseId,.name,.conclusion,.url] | @tsv'
```
3. Fetch one failed job log. If rate-limited, note reset time and avoid more REST calls.
4. For secret-looking failures, validate the provider endpoint from the same secret source before editing code.
For Docker CLI-backend failures, also validate
`OPENCLAW_CLAUDE_CREDENTIALS_JSON` or `CLAUDE_CODE_OAUTH_TOKEN` in a
clean-home Claude CLI probe; that lane should use subscription mode when
either credential exists.
4. For secret-looking failures, validate a real completion from the same secret source before editing code. A successful model-list request is insufficient.
Claude CLI subscription credentials are a separate native auth path; prove
them in a clean-home CLI probe, never as a substitute for a required
Anthropic API-key lane.
5. For live-cache failures, inspect whether it is missing/invalid key, empty text, provider refusal, timeout, or baseline miss. Do not weaken release gates without clear provider evidence.
6. Fix narrowly, run local/changed proof, commit, push, rerun the smallest matching group.

View file

@ -1,17 +1,22 @@
#!/usr/bin/env node
/**
* Release preflight helper that verifies required provider API keys can reach
* their model-list endpoints without printing secret values.
* Release preflight helper that verifies required provider API keys without
* printing secret values. Anthropic must complete a prompt because model-list
* access does not prove billing or inference entitlement.
*/
import process from "node:process";
const args = new Map();
for (let index = 2; index < process.argv.length; index += 1) {
const arg = process.argv[index];
if (!arg.startsWith("--")) continue;
if (!arg.startsWith("--")) {
continue;
}
const [key, inlineValue] = arg.slice(2).split("=", 2);
const value = inlineValue ?? process.argv[index + 1];
if (inlineValue === undefined) index += 1;
if (inlineValue === undefined) {
index += 1;
}
args.set(key, value);
}
@ -28,7 +33,9 @@ const timeoutMs = Number(args.get("timeout-ms") ?? 10_000);
function envFirst(names) {
for (const name of names) {
const value = process.env[name]?.trim();
if (value) return { name, value };
if (value) {
return { name, value };
}
}
return undefined;
}
@ -42,15 +49,21 @@ async function checkProvider(id, config) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const headers = config.headers(secret);
const headers = config.headers(secret.value);
const response = await fetch(config.url, {
body: config.body,
headers,
method: config.method,
signal: controller.signal,
});
const responseBody = config.validateResponse
? await response.json().catch(() => undefined)
: undefined;
const ok = response.ok && (!config.validateResponse || config.validateResponse(responseBody));
return {
id,
ok: response.ok,
status: response.ok ? "ok" : `http_${response.status}`,
ok,
status: response.ok ? (ok ? "ok" : "invalid_response") : `http_${response.status}`,
env: secret.name,
};
} catch (error) {
@ -69,32 +82,35 @@ const providers = {
openai: {
env: ["OPENAI_API_KEY"],
url: "https://api.openai.com/v1/models",
headers: ({ value }) => ({ authorization: `Bearer ${value}` }),
headers: (token) => ({ authorization: `Bearer ${token}` }),
},
anthropic: {
env: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_API_TOKEN"],
url: "https://api.anthropic.com/v1/models",
headers: ({ name, value }) =>
name === "ANTHROPIC_OAUTH_TOKEN"
? {
"anthropic-beta": "oauth-2025-04-20",
"anthropic-version": "2023-06-01",
authorization: `Bearer ${value}`,
}
: {
"anthropic-version": "2023-06-01",
"x-api-key": value,
},
env: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_TOKEN"],
url: "https://api.anthropic.com/v1/messages",
method: "POST",
body: JSON.stringify({
max_tokens: 8,
messages: [{ role: "user", content: "Reply with OK." }],
model: "claude-haiku-4-5",
}),
headers: (token) => ({
"anthropic-version": "2023-06-01",
"content-type": "application/json",
"x-api-key": token,
}),
validateResponse: (body) =>
Array.isArray(body?.content) &&
body.content.some((part) => typeof part?.text === "string" && part.text.trim()),
},
fireworks: {
env: ["FIREWORKS_API_KEY"],
url: "https://api.fireworks.ai/inference/v1/models",
headers: ({ value }) => ({ authorization: `Bearer ${value}` }),
headers: (token) => ({ authorization: `Bearer ${token}` }),
},
openrouter: {
env: ["OPENROUTER_API_KEY"],
url: "https://openrouter.ai/api/v1/models",
headers: ({ value }) => ({ authorization: `Bearer ${value}` }),
headers: (token) => ({ authorization: `Bearer ${token}` }),
},
};
@ -115,7 +131,9 @@ let failed = false;
for (const result of results) {
const requiredLabel = required.has(result.id) ? "required" : "optional";
console.log(`${result.id}: ${result.status} env=${result.env} ${requiredLabel}`);
if (required.has(result.id) && !result.ok) failed = true;
if (required.has(result.id) && !result.ok) {
failed = true;
}
}
if (failed) {

View file

@ -210,7 +210,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}

View file

@ -128,7 +128,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}

View file

@ -113,7 +113,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}

View file

@ -663,7 +663,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}

View file

@ -229,8 +229,6 @@ on:
required: false
ANTHROPIC_API_TOKEN:
required: false
ANTHROPIC_OAUTH_TOKEN:
required: false
FACTORY_API_KEY:
required: false
BYTEPLUS_API_KEY:
@ -521,7 +519,6 @@ jobs:
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
OPENCLAW_LIVE_CACHE_TEST: "1"
OPENCLAW_LIVE_TEST: "1"
steps:
@ -544,13 +541,10 @@ jobs:
echo "Missing OPENAI_API_KEY secret for live-cache validation." >&2
exit 1
fi
if [[ -z "${ANTHROPIC_OAUTH_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "Missing ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY secret for live-cache validation." >&2
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "Missing ANTHROPIC_API_KEY secret for live-cache validation." >&2
exit 1
fi
if [[ -n "${ANTHROPIC_OAUTH_TOKEN:-}" ]]; then
echo "ANTHROPIC_API_KEY=" >> "$GITHUB_ENV"
fi
- name: Verify live prompt cache floors
run: |
@ -686,7 +680,6 @@ jobs:
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
@ -951,7 +944,6 @@ jobs:
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
@ -1663,7 +1655,6 @@ jobs:
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
@ -1755,7 +1746,7 @@ jobs:
}
case "${LIVE_MODEL_PROVIDERS}" in
anthropic) require_any Anthropic ANTHROPIC_OAUTH_TOKEN ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;
anthropic) require_any Anthropic ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;
google) require_any Google GEMINI_API_KEY GOOGLE_API_KEY ;;
minimax) require_any MiniMax MINIMAX_API_KEY ;;
moonshot) require_any Moonshot MOONSHOT_API_KEY KIMI_API_KEY ;;
@ -1787,7 +1778,6 @@ jobs:
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
@ -1931,7 +1921,7 @@ jobs:
IFS=',' read -r -a providers <<<"${OPENCLAW_LIVE_PROVIDERS}"
for provider in "${providers[@]}"; do
case "$provider" in
anthropic) require_any Anthropic ANTHROPIC_OAUTH_TOKEN ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;
anthropic) require_any Anthropic ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;
google) require_any Google GEMINI_API_KEY GOOGLE_API_KEY ;;
minimax) require_any MiniMax MINIMAX_API_KEY ;;
moonshot) require_any Moonshot MOONSHOT_API_KEY KIMI_API_KEY ;;
@ -2150,7 +2140,6 @@ jobs:
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
@ -2371,7 +2360,6 @@ jobs:
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
@ -2588,7 +2576,6 @@ jobs:
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}

View file

@ -631,7 +631,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
@ -725,7 +724,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}

View file

@ -38,7 +38,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}

View file

@ -203,8 +203,6 @@ on:
required: false
ANTHROPIC_API_TOKEN:
required: false
ANTHROPIC_OAUTH_TOKEN:
required: false
FACTORY_API_KEY:
required: false
BYTEPLUS_API_KEY:
@ -590,7 +588,6 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}

View file

@ -34,7 +34,6 @@ for env_key in \
ANTHROPIC_API_KEY \
ANTHROPIC_API_KEY_OLD \
ANTHROPIC_API_TOKEN \
ANTHROPIC_OAUTH_TOKEN \
BYTEPLUS_API_KEY \
CEREBRAS_API_KEY \
DEEPINFRA_API_KEY \

View file

@ -991,14 +991,11 @@ describe("package artifact reuse", () => {
const providerVerifier = readFileSync(VERIFY_PROVIDER_SECRETS_SCRIPT, "utf8");
expect(hydrateScript).toContain(" FACTORY_API_KEY \\");
expect(hydrateScript).toContain(" ANTHROPIC_OAUTH_TOKEN \\");
expect(providerVerifier).toContain(
'env: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_API_TOKEN"]',
);
expect(providerVerifier).toContain('"anthropic-beta": "oauth-2025-04-20"');
expect(providerVerifier).toContain("authorization: `Bearer ${value}`");
expect(providerVerifier).toContain('url: "https://api.anthropic.com/v1/messages"');
expect(providerVerifier).toContain('model: "claude-haiku-4-5"');
expect(providerVerifier).toContain("validateResponse:");
expect(providerVerifier).not.toContain("ANTHROPIC_OAUTH_TOKEN");
expect(dockerPlanAction).toContain('if [[ "$credentials" == *",factory,"* ]]; then');
expect(dockerPlanAction).not.toContain("require_any Anthropic ANTHROPIC_OAUTH_TOKEN");
expectTextToIncludeAll(dockerPlanAction, [
'if [[ "$credentials" == *",openai,"* ]]; then',
"require_any OpenAI OPENAI_API_KEY",
@ -1021,12 +1018,9 @@ describe("package artifact reuse", () => {
testboxWorkflow,
]) {
expect(workflow).toContain("FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}");
expect(workflow).toContain("ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}");
}
expect(reusableWorkflow).toContain("FACTORY_API_KEY:\n required: false");
expect(reusableWorkflow).toContain("ANTHROPIC_OAUTH_TOKEN:\n required: false");
expect(packageAcceptanceWorkflow).toContain("FACTORY_API_KEY:\n required: false");
expect(packageAcceptanceWorkflow).toContain("ANTHROPIC_OAUTH_TOKEN:\n required: false");
expectTextToIncludeAll(reusableWorkflow, [
'if [[ "$credentials" == *",openai,"* ]]; then',
"require_any OpenAI OPENAI_API_KEY",
@ -1037,10 +1031,6 @@ describe("package artifact reuse", () => {
'if [[ "$credentials" == *",opencode,"* ]]; then',
"require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY",
]);
expect(reusableWorkflow).toContain(
"anthropic) require_any Anthropic ANTHROPIC_OAUTH_TOKEN ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;",
);
expect(reusableWorkflow).toContain('echo "ANTHROPIC_API_KEY=" >> "$GITHUB_ENV"');
expect(reusableWorkflow.match(/OPENCLAW_LIVE_CLI_BACKEND_AUTH=subscription/g)).toHaveLength(2);
expect(
reusableWorkflow.match(
@ -1118,7 +1108,6 @@ describe("package artifact reuse", () => {
);
expect(workflow).toContain("ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}");
expect(workflow).toContain("ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}");
expect(workflow).toContain("ANTHROPIC_OAUTH_TOKEN: ${{ secrets.ANTHROPIC_OAUTH_TOKEN }}");
expect(workflow).toContain(
"OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}",
);