Skip to content

feat: Add scheduled/cron trigger support for periodic repository scans#10

Merged
dcramer merged 1 commit intomainfrom
feat/schedule-trigger-support
Jan 28, 2026
Merged

feat: Add scheduled/cron trigger support for periodic repository scans#10
dcramer merged 1 commit intomainfrom
feat/schedule-trigger-support

Conversation

@jshchnz
Copy link
Member

@jshchnz jshchnz commented Jan 28, 2026

Summary

This PR adds support for scheduled/cron-based skill execution in Warden. This enables periodic repository scans that:

  • Analyze files matching configured path patterns - Use glob patterns to target specific files for analysis
  • Report findings via GitHub Issues - Creates or updates a tracking issue with a detailed findings summary
  • Optionally create fix PRs - When skills provide suggested fixes, automatically creates PRs with those fixes applied

Motivation

Previously, Warden could only run skills in response to PR events. This limited its usefulness for:

  • Weekly/monthly security audits
  • Periodic code quality scans
  • Compliance checks that need to run on the entire codebase
  • Finding issues in code that was merged before Warden was set up

With scheduled triggers, teams can now run comprehensive repository-wide scans on a regular cadence.

Key Design Insight

The existing runner already supports file-based analysis. The CLI's buildFileEventContext() creates a synthetic pullRequest context from file globs, and the SDK runner processes it normally because the files have patch data.

For scheduled runs, we adapt this pattern:

  1. Build context from paths filter using real repo info (not synthetic "local" values)
  2. Run skills through the existing runner (no modifications needed)
  3. Output to GitHub Issues instead of PR reviews
  4. Optionally create fix PRs using Git API

New Features

1. New schedule Event Type

Triggers can now use event = "schedule" to run on GitHub Actions schedule or workflow_dispatch events:

[[triggers]]
name = "weekly-security-scan"
event = "schedule"
skill = "security-review"

[triggers.filters]
paths = ["src/**/*.ts", "src/**/*.js"]
ignorePaths = ["**/*.test.ts", "**/__tests__/**"]

2. Schedule-Specific Configuration

New [triggers.schedule] section for schedule-specific options:

[triggers.schedule]
issueTitle = "Weekly Security Scan Results"  # Custom issue title
createFixPR = true                            # Auto-create fix PRs
fixBranchPrefix = "security-fix"              # Branch prefix for fix PRs

3. GitHub Issue Reporting

Findings are rendered as a comprehensive GitHub Issue with:

  • Run timestamp and commit SHA
  • Severity summary table (Critical: X, High: Y, etc.)
  • Findings grouped by file with line links
  • Each finding includes title, severity badge, description
  • Suggested fixes noted inline
  • Warden branding footer

If an issue with the same title already exists, it's updated rather than creating duplicates.

4. Automated Fix PRs

When createFixPR = true and skills provide suggestedFix.diff:

  • Collects all fixable findings
  • Applies diffs to create modified files
  • Creates a new branch via GitHub Git API
  • Opens a PR with a summary of applied fixes

Files Changed

New Files

File Purpose
src/event/schedule-context.ts Builds EventContext for scheduled runs from file globs
src/output/issue-renderer.ts Renders skill reports as GitHub Issue markdown
src/output/github-issues.ts Creates/updates issues and creates fix PRs via GitHub API

Modified Files

File Changes
src/config/schema.ts Add schedule event, ScheduleConfigSchema, validation refinements
src/types/index.ts Add schedule to GitHubEventTypeSchema
src/triggers/matcher.ts Handle schedule event matching (skip action check)
src/action/main.ts Route schedule events, add runScheduledAnalysis() handler
src/event/index.ts Export new schedule-context module
src/output/index.ts Export new issue-renderer and github-issues modules

Example Configuration

warden.toml

version = 1

[[triggers]]
name = "weekly-security-scan"
event = "schedule"
skill = "security-review"

[triggers.filters]
paths = ["src/**/*.ts", "src/**/*.js"]
ignorePaths = ["**/*.test.ts", "**/__tests__/**"]

[triggers.schedule]
issueTitle = "Weekly Security Scan Results"
createFixPR = true
fixBranchPrefix = "security-fix"

[triggers.output]
failOn = "high"
labels = ["security", "automated"]

.github/workflows/warden-scheduled.yml

name: Warden Scheduled Scan

on:
  schedule:
    - cron: '0 0 * * 0'  # Every Sunday at midnight
  workflow_dispatch:      # Allow manual trigger

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: getsentry/warden-action@v1
        with:
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

Edge Cases Handled

Case Handling
No findings Skip issue creation, log info message
Issue exists, no new findings Update issue with "no issues found" status
No files match paths filter Skip trigger with warning
Fix PR branch exists Add timestamp suffix to make unique
Diff application fails Log error, continue with other fixes
Rate limiting Octokit handles exponential backoff

Schema Validation

The schema enforces:

  • actions is required for non-schedule events
  • filters.paths is required for schedule events (must specify what to scan)
// actions is required unless event is 'schedule'
.refine((data) => {
  if (data.event !== 'schedule') {
    return data.actions !== undefined && data.actions.length > 0;
  }
  return true;
})

// paths filter is required for schedule events
.refine((data) => {
  if (data.event === 'schedule') {
    return data.filters?.paths !== undefined && data.filters.paths.length > 0;
  }
  return true;
})

Test Plan

  • All existing tests pass (185 tests)
  • TypeScript compilation succeeds
  • ESLint passes
  • Manual test: Create schedule trigger in test repo
  • Manual test: Run with workflow_dispatch event
  • Manual test: Verify issue creation/update behavior
  • Manual test: Verify fix PR creation with applied diffs

🤖 Generated with Claude Code

This adds support for running skills on a schedule (cron), enabling periodic
repository scans that analyze files matching configured path patterns, report
findings via GitHub Issues, and optionally create fix PRs.

Key changes:
- Add 'schedule' event type to trigger configuration
- Add ScheduleConfigSchema for schedule-specific options
- Create schedule context builder for file-based analysis
- Add issue renderer for GitHub Issue formatting
- Add GitHub Issues module for issue/PR creation
- Route schedule/workflow_dispatch events in action

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Jan 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
warden Ready Ready Preview, Comment Jan 28, 2026 10:53pm

Request Review

@dcramer dcramer merged commit 8f83e64 into main Jan 28, 2026
5 checks passed
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

const path = f.location?.path ?? 'unknown';
const line = f.location?.startLine ?? 0;
return `- **${f.title}** (${path}:${line})`;
}),
Copy link

Choose a reason for hiding this comment

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

PR body lists all fixable findings, not just applied

Medium Severity

The PR body generation uses fixable.map() to list all findings that have a diff and path, but fixCount only increments when applyDiffToContent succeeds. If some fixes fail (caught at line 200), the PR will state "contains X automated fix(es)" but list more findings than were actually applied. This misleads reviewers about what changes the PR contains.

Additional Locations (1)

Fix in Cursor Fix in Web

});

const matching = issues.find((issue) => issue.title === title);
return matching ? { number: matching.number, html_url: matching.html_url } : null;
Copy link

Choose a reason for hiding this comment

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

Issue search may incorrectly match pull requests

Medium Severity

The findExistingIssue function uses octokit.issues.listForRepo which returns both issues and pull requests (GitHub treats PRs as issues in the API). The code doesn't filter out PRs before matching by title. If a PR exists with the same title as the tracking issue, it may be found instead, causing subsequent updates to modify the wrong entity.

Fix in Cursor Fix in Web

repo,
state: 'open',
per_page: 100,
});
Copy link

Choose a reason for hiding this comment

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

Missing pagination causes duplicate tracking issues in large repos

Medium Severity

The findExistingIssue function only fetches the first 100 open issues without pagination. GitHub's API returns issues sorted by creation date descending (newest first). If a repository has more than 100 open issues and the Warden tracking issue was created earlier, it won't appear in the first 100 results. This causes the function to return null, leading to duplicate tracking issues being created on subsequent scheduled runs instead of updating the existing one.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants