Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions packages/docs/src/content/docs/config/models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,49 @@ Examples:
| --- | --- |
| `openai/gpt-5.5` | `WARDEN_OPENAI_API_KEY` |
| `anthropic/claude-sonnet-4-6` | `WARDEN_ANTHROPIC_API_KEY` |
| `google/<gemini-model-id>` | `WARDEN_GEMINI_API_KEY` |
| `fireworks/accounts/fireworks/models/kimi-k2p6` | `WARDEN_FIREWORKS_API_KEY` |
| `groq/llama-3.3-70b-versatile` | `WARDEN_GROQ_API_KEY` |
| `openrouter/meta-llama/llama-3.3-70b-instruct` | `WARDEN_OPENROUTER_API_KEY` |
| `vercel-ai-gateway/<model-id>` | `WARDEN_AI_GATEWAY_API_KEY` or `WARDEN_VERCEL_AI_GATEWAY_API_KEY` |
| `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` | `WARDEN_TOGETHER_API_KEY` |
| `cloudflare-workers-ai/@cf/<model-id>` | `WARDEN_CLOUDFLARE_API_KEY` + `WARDEN_CLOUDFLARE_ACCOUNT_ID` |
| `cloudflare-ai-gateway/workers-ai/@cf/<model-id>` | `WARDEN_CLOUDFLARE_API_KEY` + `WARDEN_CLOUDFLARE_ACCOUNT_ID` + `WARDEN_CLOUDFLARE_GATEWAY_ID` |
| `cloudflare-ai-gateway/<openai-model-id>` | `WARDEN_CLOUDFLARE_API_KEY` + `WARDEN_CLOUDFLARE_ACCOUNT_ID` + `WARDEN_CLOUDFLARE_GATEWAY_ID` |
| `cloudflare-ai-gateway/<anthropic-model-id>` | `WARDEN_CLOUDFLARE_API_KEY` + `WARDEN_CLOUDFLARE_ACCOUNT_ID` + `WARDEN_CLOUDFLARE_GATEWAY_ID` |

Warden splits Pi selectors at the first `/`. The provider comes before it and the
provider-specific model ID comes after it, so model IDs may contain additional
slashes.

**Notes on specific providers:**

- **Google Gemini:** Pi's provider name is `google`, not `gemini`. The credential env var is
`GEMINI_API_KEY` (or `WARDEN_GEMINI_API_KEY`). Use `google/<model-id>`, not `gemini/<model-id>`.

- **Vercel AI Gateway:** Pi's env var is `AI_GATEWAY_API_KEY`. The canonical Warden alias is
`WARDEN_AI_GATEWAY_API_KEY`. `WARDEN_VERCEL_AI_GATEWAY_API_KEY` is also accepted as a
convenience alias and will be bridged automatically.

- **Cloudflare Workers AI:** Requires both `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID`.
Use the `@cf/provider/model-id` model format: e.g. `cloudflare-workers-ai/@cf/moonshotai/kimi-k2.6`.

- **Cloudflare AI Gateway:** Routes OpenAI/Anthropic models using their native IDs
(e.g. `cloudflare-ai-gateway/gpt-5.1`, `cloudflare-ai-gateway/claude-sonnet-4-5`).
Workers AI models use the `workers-ai/@cf/...` prefix. Upstream credentials are managed in
Cloudflare's AI Gateway dashboard (unified billing, stored BYOK) — Warden's provider API keys
are not forwarded through the gateway.

**Credential convention:** Set `WARDEN_{PROVIDER}_API_KEY` to authenticate with a provider.
Warden mirrors these to the native `{PROVIDER}_API_KEY` expected by each SDK at runtime.
If you already have a native provider key set (e.g. `OPENAI_API_KEY`), Warden will use it
directly and the `WARDEN_`-prefixed form is not required.

**Multi-part credentials:** Some providers require additional env vars beyond an API key.
Cloudflare providers require `CLOUDFLARE_ACCOUNT_ID` (or `WARDEN_CLOUDFLARE_ACCOUNT_ID`).
Cloudflare AI Gateway additionally requires `CLOUDFLARE_GATEWAY_ID` (or `WARDEN_CLOUDFLARE_GATEWAY_ID`).
Warden mirrors these automatically when the `WARDEN_`-prefixed form is set.

## Claude Runtime Models

When `runtime = "claude"`, use the model IDs accepted by Claude Code:
Expand Down
7 changes: 6 additions & 1 deletion packages/warden/src/action/triggers/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { SkillRunnerError } from '../../sdk/errors.js';
import type { Semaphore } from '../../utils/index.js';
import { Verbosity } from '../../cli/output/verbosity.js';
import type { ProviderFailureCircuitBreaker } from '../../sdk/circuit-breaker.js';
import { assertValidPiModelSelectors } from '../../sdk/runtimes/model-selectors.js';
import { assertValidPiModelSelectors, findMissingCloudflareEnv, missingCloudflareEnvMessage } from '../../sdk/runtimes/model-selectors.js';
import { captureActionTriggerError } from '../error-reporting.js';

/** Log-mode output for CI: no TTY, no color. */
Expand Down Expand Up @@ -135,6 +135,11 @@ export async function executeTrigger(
try {
assertValidPiModelSelectors([trigger]);

const missingCloudflare = findMissingCloudflareEnv([trigger]);
if (missingCloudflare) {
throw new Error(missingCloudflareEnvMessage(missingCloudflare));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cloudflare preflight uses generic Error

Medium Severity

Missing Cloudflare env preflight throws a plain Error instead of a typed error with a stable code. captureActionTriggerError classifies that as unknown, so CI failures for unset account or gateway IDs are not grouped like invalid_model_selector preflight errors.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 89a4d61. Configure here.

}

const taskOptions: SkillTaskOptions = {
name: trigger.name,
displayName: trigger.skill,
Expand Down
7 changes: 6 additions & 1 deletion packages/warden/src/action/workflow/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { LayeredSkillRootsByName, ResolvedTrigger } from '../../config/load
import type { ScheduleConfig } from '../../config/schema.js';
import { buildScheduleEventContext } from '../../event/schedule-context.js';
import { runSkill } from '../../sdk/runner.js';
import { assertValidPiModelSelectors } from '../../sdk/runtimes/model-selectors.js';
import { assertValidPiModelSelectors, findMissingCloudflareEnv, missingCloudflareEnvMessage } from '../../sdk/runtimes/model-selectors.js';
import { createOrUpdateIssue } from '../../output/github-issues.js';
import { shouldFail, countFindingsAtOrAbove, countSeverity } from '../../triggers/matcher.js';
import { resolveSkillAsync } from '../../skills/loader.js';
Expand Down Expand Up @@ -179,6 +179,11 @@ async function runScheduleWorkflowInner(
try {
assertValidPiModelSelectors([resolved]);

const missingCloudflare = findMissingCloudflareEnv([resolved]);
if (missingCloudflare) {
throw new Error(missingCloudflareEnvMessage(missingCloudflare));
Comment thread
sentry-warden[bot] marked this conversation as resolved.
}

// Build context from paths filter
const patterns = resolved.filters?.paths ?? ['**/*'];
const ignorePatterns = resolved.filters?.ignorePaths;
Expand Down
17 changes: 16 additions & 1 deletion packages/warden/src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import type { SkillDefinition, WardenConfig, Effort } from '../config/schema.js'
import { verifyAuth, type WardenAuthenticationError, type SkillRunnerOptions, type ChunkAnalysisResult } from '../sdk/runner.js';
import {
findInvalidPiModelSelector as findInvalidPiModelSelectorTarget,
findMissingCloudflareEnv,
invalidPiModelSelectorMessage,
missingCloudflareEnvMessage,
piModelSelectorTip,
type InvalidPiModelSelector,
type MissingCloudflareEnv,
} from '../sdk/runtimes/model-selectors.js';
import { mapExtractionErrorCode } from '../sdk/errors.js';
import { aggregateAuxiliaryUsageAttribution, mergeAuxiliaryUsage } from '../sdk/usage.js';
Expand Down Expand Up @@ -732,7 +736,13 @@ export function findInvalidPiModelSelector(

function reportInvalidPiModelSelector(reporter: Reporter, invalid: InvalidPiModelSelector): void {
reporter.error(invalidPiModelSelectorMessage(invalid));
reporter.tip('Set a Pi model selector such as anthropic/claude-sonnet-4-6.');
reporter.tip(piModelSelectorTip(invalid.model));
}

function reportMissingCloudflareEnv(reporter: Reporter, missing: MissingCloudflareEnv): void {
reporter.error(missingCloudflareEnvMessage(missing));
const tip = missing.missing.map((v) => `export WARDEN_${v}=...`).join(' ');
reporter.tip(tip);
}

function emitInvalidPiModelSelectorRunLog(
Expand Down Expand Up @@ -1518,6 +1528,11 @@ async function runConfigMode(options: CLIOptions, reporter: Reporter): Promise<n
emitInvalidPiModelSelectorRunLog(repoPath, options, invalidModelSelector);
return 1;
}
const missingCloudflare = findMissingCloudflareEnv(specs.map((s) => ({ ...s.runnerOptions })));
Comment thread
cursor[bot] marked this conversation as resolved.
if (missingCloudflare) {
reportMissingCloudflareEnv(reporter, missingCloudflare);
return 1;
}
let tasks: SkillTaskOptions[];
const concurrency = options.parallel ?? config.runner?.concurrency ?? DEFAULT_CONCURRENCY;
try {
Expand Down
12 changes: 10 additions & 2 deletions packages/warden/src/sdk/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,9 +1144,17 @@ async function runSkillAnalysis(
{ code: 'provider_unavailable' },
);
}
// Include the first failure message so users can diagnose config problems
// without inspecting per-hunk logs. Messages are already sanitized by the
// time they reach failureMessage.
const firstFailureMsg = analysisFailures[0]?.message;
const failureDetail = firstFailureMsg
? ` First failure: ${firstFailureMsg.slice(0, 500)}.`
: '';
throw new SkillRunnerError(
`All ${totalHunks} chunk${totalHunks === 1 ? '' : 's'} failed to analyze. ` +
`This usually indicates an authentication problem. ${allHunksFailedGuidance(options.runtime)}`,
`All ${totalHunks} chunk${totalHunks === 1 ? '' : 's'} failed to analyze.${failureDetail} ` +
`This usually indicates a runtime, model, or provider configuration problem. ` +
`${allHunksFailedGuidance(options.runtime)}`,
{ code: 'all_hunks_failed' },
);
}
Expand Down
8 changes: 6 additions & 2 deletions packages/warden/src/sdk/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ https://console.anthropic.com/ for API keys`;

/** User-friendly error message for authentication failures (Pi runtime) */
const PI_AUTH_GUIDANCE = `
export WARDEN_MODEL=provider/model-id # e.g. openai/gpt-5.5
export WARDEN_{PROVIDER}_API_KEY=... # WARDEN-prefixed key for that provider
export WARDEN_MODEL=provider/model-id # e.g. openai/gpt-5.5, cloudflare-workers-ai/@cf/model
export WARDEN_{PROVIDER}_API_KEY=... # WARDEN-prefixed API key for that provider

Note: Some providers require additional env vars beyond an API key.
Cloudflare providers also require CLOUDFLARE_ACCOUNT_ID (or WARDEN_CLOUDFLARE_ACCOUNT_ID).
Cloudflare AI Gateway additionally requires CLOUDFLARE_GATEWAY_ID (or WARDEN_CLOUDFLARE_GATEWAY_ID).

See https://warden.sentry.dev/config/models for provider selectors and credential names.`;

Expand Down
Loading
Loading