[auto] #86 feat: webhook triggers — GitHub/Slack events start agent workflows - #98
[auto] #86 feat: webhook triggers — GitHub/Slack events start agent workflows#98nutt-adam wants to merge 3 commits into
Conversation
Add WebhookConfig struct to TuttiConfig with [[webhook]] TOML array parsing (source, events, workflow, agent, prompt fields). Add POST /v1/webhooks/generic endpoint in serve.rs that accepts JSON payloads, enforces a 1MB size limit, matches against configured triggers by source and event type, and dispatches via the existing workflow run or agent send infrastructure. Includes unit tests for config deserialization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe changes implement webhook support for the Tutti system by introducing a new Changes
Sequence DiagramsequenceDiagram
participant Client
participant Server as API Server
participant Webhook as Webhook Handler
participant TriggerMatcher as Trigger Matcher
participant Dispatcher as Dispatcher
participant EventLog as Event Logger
Client->>Server: POST /v1/webhooks/generic<br/>(JSON payload)
Server->>Webhook: Validate Content-Length
Webhook->>Webhook: Parse JSON body
Webhook->>Webhook: Extract source, event, workspace
Webhook->>TriggerMatcher: match_triggers(webhooks,<br/>source, event)
alt Triggers Match
TriggerMatcher->>Webhook: Matched configs
Webhook->>Dispatcher: Dispatch workflow or send
Dispatcher->>Dispatcher: Execute operation
Webhook->>EventLog: log_event(matched_rule,<br/>"dispatched")
Webhook->>Client: 200 OK with matched: true
else No Triggers Match
TriggerMatcher->>Webhook: Empty matches
Webhook->>EventLog: log_event(no rule,<br/>"no_match")
Webhook->>Client: 200 OK with matched: false
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…vent logging
Create src/webhook.rs with match_triggers(), expand_template(), and
log_event() — extracting trigger-matching logic from inline serve.rs
code into a dedicated module. Template expansion supports {{event.field}}
and {{event.nested.field}} placeholders resolved against the JSON
payload. Webhook events are logged to .tutti/state/webhook-events.jsonl.
Refactor route_webhook in serve.rs to use the new webhook module
functions and apply template expansion to agent prompts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/cli/serve.rs`:
- Around line 793-797: The code resolves a top-level workspace with
resolve_action_workspace before checking webhook rules, which prevents matching
rules in other workspaces; move the webhook matching call before
resolve_action_workspace so webhook::match_triggers(&target.config.webhooks,
source, event_type) (or an equivalent call that iterates all served workspace
configs) runs using the incoming payload/body and source/event_type first (using
workspace_hint only for optional narrowing), and only call
resolve_action_workspace(targets, workspace_hint) to pick a target after
matching triggers; adjust control flow around workspace_hint, payload,
webhook::match_triggers, and resolve_action_workspace accordingly.
- Around line 812-867: The webhook dispatch loop currently executes workflows or
sends unconditionally (inside the for loop over matched using wh.workflow,
super::run::run and super::send::run) which allows replay; add a delivery-key
idempotency check before dispatching each matched entry: extract a delivery key
from the incoming payload or headers (same key format used by the existing
/v1/actions/* idempotency logic), look up/record that key in the same
idempotency store used elsewhere in this file, and skip dispatch/logging/adding
to dispatched if the key already exists; implement the check at the top of the
loop (before calling with_project_root, super::run::run or super::send::run and
before webhook::log_event) and ensure you persist the delivery key after a
successful dispatch so retries are ignored.
- Around line 771-783: The current Content-Length check is insufficient because
read_json_body can still read unlimited bytes when Content-Length is
missing/invalid; update the flow to enforce WEBHOOK_MAX_BODY_BYTES at the reader
boundary by replacing or wrapping read_json_body with a bounded reader variant
(e.g., read_json_body_limited) that uses
request.as_reader().take((WEBHOOK_MAX_BODY_BYTES + 1) as u64) and reads to a
buffer, then returns a ConfigValidation error if the buffer length exceeds
WEBHOOK_MAX_BODY_BYTES, treats all-whitespace bodies as empty JSON, and finally
parses the buffer with serde_json::from_slice; call this new bounded function
where read_json_body(...) is currently invoked and keep the existing
Content-Length pre-check as a fast-fail optimization.
In `@src/config/mod.rs`:
- Around line 315-331: WebhookConfig allows ambiguous states (both or neither of
workflow/agent); add a validation method on the WebhookConfig struct (e.g., impl
WebhookConfig { pub fn validate(&self) -> Result<(), ConfigError> }) that
returns an error if both workflow and agent are set or if neither is set (and
optionally validate events is not empty), and then call this validate() from the
global config validation path after deserialization so invalid webhook configs
fail at load time rather than at runtime dispatch.
In `@src/webhook.rs`:
- Around line 6-10: The exported helpers match_triggers, expand_template, and
log_event violate the crate rule that public functions must return Result<T,
TuttiError>; either make them internal by removing pub or change their
signatures to return Result with TuttiError and propagate errors through
callers—specifically: for match_triggers and expand_template update their return
types to Result<Vec<&WebhookConfig>, TuttiError> (or appropriate Result<T,
TuttiError>) and adjust callers to handle the Result, and for log_event stop
swallowing append failures by returning Result<(), TuttiError> and propagating
any underlying I/O/logging errors so callers can choose best-effort vs required
behavior; ensure TuttiError variants cover the new failure cases and update all
call sites to handle the Result.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 7513b808-3950-4bf5-b53b-b2590c301285
📒 Files selected for processing (10)
src/automation/mod.rssrc/budget/mod.rssrc/cli/doctor.rssrc/cli/handoff.rssrc/cli/serve.rssrc/cli/up.rssrc/cli/watch.rssrc/config/mod.rssrc/main.rssrc/webhook.rs
1. Enforce 1 MiB body cap via .take() on the reader, not just Content-Length header check (serve.rs read_json_body). 2. Match webhook triggers before resolving workspace — scan all targets for matching triggers, then resolve workspace from the match. 3. Add replay protection using delivery ID headers (X-GitHub-Delivery, X-Delivery-ID, X-Request-Id, Idempotency-Key) or SHA-256 payload hash, with a persistent replay store capped at 10k entries. 4. Add webhook config validation: source non-empty, workflow/agent mutual exclusivity, referenced names exist, reject prompt on workflow webhooks. 5. Make webhook module public functions return Result — log_event, is_replay, and record_delivery now propagate errors properly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Automated SDLC cycle for #86.
Summary by CodeRabbit
POST /v1/webhooks/generic) to receive events and trigger configured workflows or send operations