Skip to main content
The buildEventContext() function transforms GitHub webhook events into a normalized EventContext structure that Warden uses for skill execution.

Function Signature

async function buildEventContext(
  eventName: string,
  eventPayload: unknown,
  repoPath: string,
  octokit: Octokit
): Promise<EventContext>
eventName
string
required
GitHub event type (e.g., 'pull_request', 'schedule').In GitHub Actions, read from github.event_name:
const eventName = process.env.GITHUB_EVENT_NAME || 'pull_request';
eventPayload
unknown
required
GitHub webhook payload. In Actions, read from GITHUB_EVENT_PATH:
import { readFileSync } from 'fs';
const eventPath = process.env.GITHUB_EVENT_PATH!;
const eventPayload = JSON.parse(readFileSync(eventPath, 'utf-8'));
repoPath
string
required
Absolute path to the cloned repository on disk.In Actions:
const repoPath = process.env.GITHUB_WORKSPACE!;
octokit
Octokit
required
Authenticated Octokit instance for fetching PR file metadata:
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({ 
  auth: process.env.GITHUB_TOKEN 
});

Returns

EventContext
object
Normalized event context:
interface EventContext {
  eventType: GitHubEventType;
  action: string;
  repository: RepositoryContext;
  pullRequest?: PullRequestContext;
  repoPath: string;
}

EventContext Fields

eventType
GitHubEventType
GitHub event type:
  • 'pull_request'
  • 'schedule'
  • 'issues'
  • 'issue_comment'
  • 'pull_request_review'
  • 'pull_request_review_comment'
action
string
Event action (e.g., 'opened', 'synchronize', 'reopened').
repository
RepositoryContext
Repository metadata:
interface RepositoryContext {
  owner: string;          // e.g., "getsentry"
  name: string;           // e.g., "warden"
  fullName: string;       // e.g., "getsentry/warden"
  defaultBranch: string;  // e.g., "main"
}
pullRequest
PullRequestContext | undefined
Pull request context (only present for eventType === 'pull_request'):
interface PullRequestContext {
  number: number;
  title: string;
  body: string | null;
  author: string;
  baseBranch: string;
  headBranch: string;
  headSha: string;
  baseSha: string;
  files: FileChange[];
}
repoPath
string
Absolute path to the repository on disk.

FileChange Fields

FileChange
object
Per-file change metadata:
interface FileChange {
  filename: string;
  status: 'added' | 'removed' | 'modified' | 'renamed' | 'copied' | 'changed' | 'unchanged';
  additions: number;
  deletions: number;
  patch?: string;  // Unified diff patch
}

Example: GitHub Actions

Minimal GitHub Action integration:
import { buildEventContext } from '@sentry/warden';
import { Octokit } from '@octokit/rest';
import { readFileSync } from 'fs';

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const repoPath = process.env.GITHUB_WORKSPACE!;
const eventName = process.env.GITHUB_EVENT_NAME!;
const eventPayload = JSON.parse(
  readFileSync(process.env.GITHUB_EVENT_PATH!, 'utf-8')
);

const context = await buildEventContext(
  eventName,
  eventPayload,
  repoPath,
  octokit
);

console.log(`PR #${context.pullRequest?.number}: ${context.pullRequest?.title}`);
console.log(`Files changed: ${context.pullRequest?.files.length}`);

Example: Webhook Server

Handle webhook events in a Node.js server:
import express from 'express';
import { buildEventContext } from '@sentry/warden';
import { Octokit } from '@octokit/rest';
import { execSync } from 'child_process';
import { mkdtempSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';

const app = express();
app.use(express.json());

app.post('/webhooks/github', async (req, res) => {
  const eventName = req.headers['x-github-event'] as string;
  const eventPayload = req.body;
  
  // Clone repo to temp dir
  const repoPath = mkdtempSync(join(tmpdir(), 'warden-'));
  const cloneUrl = eventPayload.repository.clone_url;
  execSync(`git clone ${cloneUrl} ${repoPath}`);
  
  // Build context
  const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
  const context = await buildEventContext(
    eventName,
    eventPayload,
    repoPath,
    octokit
  );
  
  // Process event
  // ...
  
  res.status(200).send('OK');
});

app.listen(3000);

Example: Filter Context by Paths

Combine with filterContextByPaths() to apply path filters:
import { 
  buildEventContext, 
  filterContextByPaths 
} from '@sentry/warden';

const context = await buildEventContext(
  eventName,
  eventPayload,
  repoPath,
  octokit
);

// Filter to only TypeScript files
const tsContext = filterContextByPaths(context, {
  paths: ['**/*.ts', '**/*.tsx'],
  ignorePaths: ['**/test/**'],
});

console.log(`Filtered: ${tsContext.pullRequest?.files.length} TS files`);

Error Handling

Throws EventContextError on validation failures:
import { 
  buildEventContext, 
  EventContextError 
} from '@sentry/warden';

try {
  const context = await buildEventContext(
    eventName,
    eventPayload,
    repoPath,
    octokit
  );
} catch (error) {
  if (error instanceof EventContextError) {
    console.error('Invalid event payload:', error.message);
    // Possible causes:
    // - Missing required fields (repository, pull_request, etc.)
    // - Invalid field types
    // - Malformed webhook payload
  }
}

Validation

The function validates:
  • Event payload structure - Required fields present and correctly typed
  • Repository metadata - Owner, name, default branch
  • Pull request data - Number, title, base/head refs (if present)
  • Final context - Complete structure matches EventContextSchema

API Calls

For pull_request events, the function makes paginated API calls to fetch file metadata:
// Fetches all changed files with pagination
const files = await octokit.paginate(octokit.pulls.listFiles, {
  owner,
  repo,
  pull_number,
  per_page: 100,
});
Each file includes:
  • Filename and status
  • Additions/deletions counts
  • Unified diff patch (if available)

Non-PR Events

For non-PR events (e.g., schedule), pullRequest is undefined:
const context = await buildEventContext(
  'schedule',
  schedulePayload,
  repoPath,
  octokit
);

if (context.pullRequest) {
  // PR-specific logic
} else {
  // Schedule or other event types
}

Context Usage

The returned context is passed to:
import { 
  buildEventContext,
  loadWardenConfig,
  resolveSkillConfigs,
  matchTrigger,
  resolveSkillAsync,
  runSkill,
} from '@sentry/warden';

// Build context
const context = await buildEventContext(
  eventName,
  eventPayload,
  repoPath,
  octokit
);

// Load config and match triggers
const config = loadWardenConfig(repoPath);
const triggers = resolveSkillConfigs(config);
const matchedTriggers = triggers.filter(t => 
  matchTrigger(t, context, 'github')
);

// Run matched skills
for (const trigger of matchedTriggers) {
  const skill = await resolveSkillAsync(trigger.name, repoPath);
  const report = await runSkill(skill, context, {
    model: trigger.model,
    failOn: trigger.failOn,
  });
}

Build docs developers (and LLMs) love