Skip to main content
Warden’s configuration system parses warden.toml files and resolves skill configurations into executable triggers with merged defaults.

loadWardenConfig()

Load and validate a warden.toml configuration file.

Function Signature

function loadWardenConfig(repoPath: string): WardenConfig
repoPath
string
required
Path to the repository root. The function looks for warden.toml in this directory.

Returns

WardenConfig
object
Parsed and validated configuration object:
interface WardenConfig {
  version: 1;
  defaults?: Defaults;
  skills: SkillConfig[];
  runner?: RunnerConfig;
  logs?: LogsConfig;
}

Example

import { loadWardenConfig } from '@sentry/warden';

try {
  const config = loadWardenConfig('/path/to/repo');
  console.log(`Loaded ${config.skills.length} skills`);
} catch (error) {
  if (error instanceof ConfigLoadError) {
    console.error('Invalid config:', error.message);
  }
}

Error Handling

Throws ConfigLoadError with detailed diagnostics:
import { loadWardenConfig, ConfigLoadError } from '@sentry/warden';

try {
  const config = loadWardenConfig(repoPath);
} catch (error) {
  if (error instanceof ConfigLoadError) {
    // Error types:
    // - "Configuration file not found"
    // - "Failed to read configuration file"
    // - "Failed to parse TOML configuration"
    // - "Invalid configuration" (with validation details)
    console.error(error.message);
  }
}

Legacy Format Detection

Detects and provides migration guidance for the old [[triggers]] format:
# ❌ Legacy format (detected and rejected)
[[triggers]]
name = "my-skill"
event = "pull_request"
actions = ["opened", "synchronize"]
Error message includes migration path:
Legacy [[triggers]] format detected. Migrate to [[skills]] format:

  [[triggers]]               →  [[skills]]
  name = "my-skill"              name = "my-skill"
  event = "pull_request"     →  [[skills.triggers]]
  skill = "my-skill"              type = "pull_request"
  actions = [...]                 actions = [...]

Validation

The function validates:
  • File existence - warden.toml must exist in repo root
  • TOML syntax - Valid TOML format
  • Schema conformance - All fields match expected types
  • Duplicate names - Skill names must be unique
  • Schedule paths - Skills with type = "schedule" must define paths
  • PR actions - Pull request triggers must specify actions

resolveSkillConfigs()

Flattens skill configurations into executable triggers with merged defaults.

Function Signature

function resolveSkillConfigs(
  config: WardenConfig,
  cliModel?: string
): ResolvedTrigger[]
config
WardenConfig
required
Configuration loaded from loadWardenConfig()
cliModel
string
Model specified via CLI flag (e.g., --model). Used in model precedence chain.

Returns

ResolvedTrigger[]
ResolvedTrigger[]
Array of resolved triggers, one per skill × trigger combination:
interface ResolvedTrigger {
  name: string;                    // Skill name
  skill: string;                   // Same as name
  type: TriggerType | '*';         // 'pull_request', 'local', 'schedule', or wildcard
  actions?: string[];              // PR actions (for pull_request type)
  remote?: string;                 // Remote repo reference
  filters: {
    paths?: string[];              // Include patterns
    ignorePaths?: string[];        // Exclude patterns
  };
  // Merged output options
  failOn?: SeverityThreshold;
  reportOn?: SeverityThreshold;
  maxFindings?: number;
  reportOnSuccess?: boolean;
  requestChanges?: boolean;
  failCheck?: boolean;
  model?: string;
  maxTurns?: number;
  minConfidence?: ConfidenceThreshold;
  schedule?: ScheduleConfig;
}

Example

import { loadWardenConfig, resolveSkillConfigs } from '@sentry/warden';

const config = loadWardenConfig('/path/to/repo');
const triggers = resolveSkillConfigs(config, 'claude-sonnet-4-20250514');

for (const trigger of triggers) {
  console.log(`${trigger.name} (${trigger.type})`);
  console.log(`  Model: ${trigger.model}`);
  console.log(`  Fail on: ${trigger.failOn ?? 'off'}`);
}

Wildcard Triggers

Skills without [[skills.triggers]] produce a wildcard entry (type: '*'):
[[skills]]
name = "security-review"
# No triggers = runs everywhere
Resolved as:
{
  name: "security-review",
  type: "*",
  filters: { ignorePaths: [...] },
  // ... merged defaults
}

Multi-Trigger Skills

Each [[skills.triggers]] entry generates a separate ResolvedTrigger:
[[skills]]
name = "security-review"

[[skills.triggers]]
type = "pull_request"
actions = ["opened", "synchronize"]
failOn = "high"

[[skills.triggers]]
type = "schedule"
failOn = "medium"
Produces two triggers:
[
  {
    name: "security-review",
    type: "pull_request",
    actions: ["opened", "synchronize"],
    failOn: "high",
  },
  {
    name: "security-review",
    type: "schedule",
    failOn: "medium",
  },
]

Model Precedence

Model selection follows a 6-level precedence chain (highest to lowest):
  1. Trigger-level model (per-trigger override)
  2. Skill-level model (per-skill default)
  3. defaults.model (global default in warden.toml)
  4. cliModel parameter (from --model flag)
  5. WARDEN_MODEL environment variable
  6. SDK default (not set by resolveSkillConfigs)
const triggers = resolveSkillConfigs(config, 'claude-opus-4-20250514');
// CLI model applies to skills without explicit model config

Path Filter Merging

ignorePaths is additive across defaults and skills:
[defaults]
ignorePaths = ["**/test/**", "**/fixtures/**"]

[[skills]]
name = "security-review"
ignorePaths = ["**/generated/**"]
Resolved as:
{
  filters: {
    ignorePaths: [
      "**/test/**",
      "**/fixtures/**",
      "**/generated/**",  // merged
    ],
  },
}

Field Inheritance

All output fields use 3-level precedence:
  • Trigger-level (highest priority)
  • Skill-level
  • Defaults (lowest priority)
Example:
[defaults]
failOn = "medium"
reportOn = "low"
maxFindings = 50

[[skills]]
name = "security-review"
failOn = "high"           # overrides defaults.failOn
# reportOn inherited from defaults

[[skills.triggers]]
type = "pull_request"
actions = ["opened"]
maxFindings = 10          # overrides skill and defaults
Resolved as:
{
  failOn: "high",          // from skill
  reportOn: "low",         // from defaults
  maxFindings: 10,         // from trigger
}

Configuration Types

WardenConfig

interface WardenConfig {
  version: 1;
  defaults?: Defaults;
  skills: SkillConfig[];
  runner?: RunnerConfig;
  logs?: LogsConfig;
}

SkillConfig

interface SkillConfig {
  name: string;
  paths?: string[];              // Include patterns
  ignorePaths?: string[];        // Exclude patterns
  remote?: string;               // Remote repo (e.g., "owner/repo@sha")
  failOn?: SeverityThreshold;
  reportOn?: SeverityThreshold;
  maxFindings?: number;
  reportOnSuccess?: boolean;
  requestChanges?: boolean;
  failCheck?: boolean;
  model?: string;
  maxTurns?: number;
  minConfidence?: ConfidenceThreshold;
  triggers?: SkillTrigger[];
}

SkillTrigger

interface SkillTrigger {
  type: 'pull_request' | 'local' | 'schedule';
  actions?: string[];            // Required for pull_request
  failOn?: SeverityThreshold;
  reportOn?: SeverityThreshold;
  maxFindings?: number;
  reportOnSuccess?: boolean;
  requestChanges?: boolean;
  failCheck?: boolean;
  model?: string;
  maxTurns?: number;
  minConfidence?: ConfidenceThreshold;
  schedule?: ScheduleConfig;
}

Defaults

interface Defaults {
  failOn?: SeverityThreshold;
  reportOn?: SeverityThreshold;
  maxFindings?: number;
  reportOnSuccess?: boolean;
  requestChanges?: boolean;
  failCheck?: boolean;
  model?: string;
  maxTurns?: number;
  minConfidence?: ConfidenceThreshold;
  ignorePaths?: string[];
  defaultBranch?: string;
  chunking?: ChunkingConfig;
  batchDelayMs?: number;
  auxiliaryMaxRetries?: number;
}

Complete Example

import { 
  loadWardenConfig, 
  resolveSkillConfigs,
  matchTrigger,
  type ResolvedTrigger 
} from '@sentry/warden';

// Load and resolve config
const config = loadWardenConfig('/path/to/repo');
const allTriggers = resolveSkillConfigs(config);

// Filter by environment
function getTriggersForEnvironment(
  triggers: ResolvedTrigger[],
  environment: 'github' | 'local'
): ResolvedTrigger[] {
  return triggers.filter(t => {
    // Wildcards match everywhere
    if (t.type === '*') return true;
    // Local triggers only in local mode
    if (t.type === 'local') return environment === 'local';
    // PR triggers match in both environments
    if (t.type === 'pull_request') return true;
    // Schedule triggers don't run in local mode
    if (t.type === 'schedule') return environment === 'github';
    return false;
  });
}

const githubTriggers = getTriggersForEnvironment(allTriggers, 'github');
console.log(`Found ${githubTriggers.length} GitHub triggers`);

// Filter by context (see matchTrigger for details)
import { buildEventContext } from '@sentry/warden';
const context = await buildEventContext(...);
const matchedTriggers = githubTriggers.filter(t => 
  matchTrigger(t, context, 'github')
);

Build docs developers (and LLMs) love