Skip to content

feat: Add notification system for scheduled triggers#167

Open
dcramer wants to merge 2 commits intomainfrom
feat/notification-system
Open

feat: Add notification system for scheduled triggers#167
dcramer wants to merge 2 commits intomainfrom
feat/notification-system

Conversation

@dcramer
Copy link
Member

@dcramer dcramer commented Feb 18, 2026

Replace the single-tracking-issue approach with a provider-based notification
layer. Scheduled findings now route through configurable providers (GitHub Issues,
Slack) with suppression rules for false positive management.

The old createOrUpdateIssue + issue-renderer approach was tightly coupled to
GitHub Issues and couldn't support additional channels. This introduces a
NotificationProvider interface so adding new providers (email, PagerDuty, etc.)
is straightforward.

Key changes:

  • Notification providers: NotificationProvider interface with GitHub Issues
    and Slack (Block Kit webhooks) implementations. GitHub Issues provider includes
    two-tier dedup (content hash + semantic similarity via Haiku)
  • Suppression system: YAML-based rules in .warden/suppressions.yaml that
    filter findings by skill, path globs, and title before they reach providers
  • Dispatcher: Orchestrates suppression filtering and sequential provider
    execution with error isolation
  • Config: New [[notifications]] table in warden.toml with discriminated
    union schema (type = "github-issues" or type = "slack")
  • Removed: issueTitle from schedule config, createOrUpdateIssue, and
    issue-renderer.ts (replaced by the provider layer)

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

@vercel
Copy link

vercel bot commented Feb 18, 2026

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

Project Deployment Actions Updated (UTC)
warden Ready Ready Preview, Comment Feb 18, 2026 0:12am

Request Review

Replace the single-tracking-issue approach with a provider-based notification
layer. Scheduled findings now route through configurable providers (GitHub Issues,
Slack) with suppression rules for false positive management.

- Add GitHub Issues provider with two-tier dedup (hash + semantic via Haiku)
- Add Slack provider with Block Kit webhook posting
- Add YAML-based suppression system (skill + glob paths + title matching)
- Add notification dispatcher to orchestrate suppression and provider execution
- Remove createOrUpdateIssue and issue-renderer (replaced by providers)
- Remove issueTitle from schedule config schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move label creation outside per-finding loop to avoid redundant API calls
- Remove duplicate notification logging (dispatcher handles its own logging)
- Eliminate redundant `anchor` variable identical to `lineRange`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +41 to +44
providers.push(new SlackProvider({
...config,
webhookUrl: expandEnvVars(config.webhookUrl),
}));
Copy link

Choose a reason for hiding this comment

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

Bug: If a Slack webhookUrl is an unset environment variable, it expands to an empty string, causing fetch to throw a TypeError and the notification to fail silently.
Severity: MEDIUM

Suggested Fix

Re-validate the webhookUrl after environment variable expansion to ensure it's a valid URL and not an empty string. Alternatively, modify expandEnvVars to throw an error for unset variables. A third option is to check the DispatchResult in schedule.ts and log a clear warning if any provider reports errors.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/notifications/index.ts#L41-L44

Potential issue: The configuration schema validates the `webhookUrl` before environment
variables are expanded. A variable like `"$SLACK_WEBHOOK_URL"` passes validation as a
non-empty string. Later, `expandEnvVars` resolves this to an empty string if the
environment variable is not set. When the Slack provider attempts to call `fetch('')`,
it throws a `TypeError`. While this error is caught by the provider and logged, the main
workflow in `schedule.ts` does not inspect the dispatch result, leading to a silent
failure where users are unaware that their notifications are not being sent.

Did we get this right? 👍 / 👎 to inform future reviews.

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 2 potential issues.

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

const text = await response.text();
result.errors.push(`Slack webhook returned ${response.status}: ${text}`);
} else {
result.sent = context.findings.length;
Copy link

Choose a reason for hiding this comment

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

Slack provider reports findings count, not messages sent

Low Severity

The SlackProvider sets result.sent = context.findings.length, but it only posts a single webhook message regardless of how many findings exist. The NotificationResult.sent field is documented as "Number of notifications sent (e.g., issues created, messages posted)." The dispatcher then logs sent ${result.sent} notification(s), so with 10 findings it would misleadingly report "slack: sent 10 notifications" when only 1 Slack message was actually posted. The value here likely needs to be 1 to match the semantics used by the GitHub Issues provider (which increments sent per actual API call).

Additional Locations (1)

Fix in Cursor Fix in Web

unmatchedFindings.map((f) => f.finding),
existingIssues,
this.apiKey
);
Copy link

Choose a reason for hiding this comment

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

Semantic dedup missing same-file filter from spec

Medium Severity

The comment on Phase 2 says "only for same-file findings" and the spec explicitly states "Semantic match via Haiku for same-file findings with no hash match," but the implementation passes all unmatched findings to findSemanticMatches without filtering by file. This sends more data to the LLM than intended, increasing cost, and risks false-positive semantic matches between unrelated findings in different files.

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.

1 participant

Comments