Skip to content

feat(webhooks): log decision reason for each webhook call#645

Merged
zbigniewsobiecki merged 2 commits intodevfrom
feature/webhook-decision-reason-logging
Mar 7, 2026
Merged

feat(webhooks): log decision reason for each webhook call#645
zbigniewsobiecki merged 2 commits intodevfrom
feature/webhook-decision-reason-logging

Conversation

@aaight
Copy link
Copy Markdown
Collaborator

@aaight aaight commented Mar 7, 2026

Summary

  • Add decision_reason TEXT column to webhook_logs DB table with migration 0027_webhook_decision_reason.sql
  • Thread decisionReason through the full pipeline: ProcessRouterWebhookResultWebhookLogOverridesWebhookLogInputInsertWebhookLogInput → DB insert
  • Collect human-readable decision reasons at every pipeline decision point in processRouterWebhook() (unparseable, self-authored, no project, no trigger, no-agent trigger, work-item locked, concurrency limit, queue failed, job queued)
  • Handle parse-failure path in createWebhookHandler factory (logs "Parse failed: <error>")
  • Propagate decisionReason from Trello/GitHub/JIRA router handlers to log overrides
  • Display in CLI: new "Reason" column in webhooklogs list, new "Decision Reason" field in webhooklogs show
  • Display in Dashboard: new "Reason" column in WebhookLogsTable and "Decision Reason" field in WebhookLogDetailDialog
  • Update unit tests: webhookLogger.test.ts, webhookHandlers.test.ts, webhook-processor.test.ts now verify decisionReason propagation at all decision points

Test plan

  • All 3923 unit tests pass
  • Lint passes (biome — 0 errors)
  • TypeScript type-check passes (0 errors)
  • Migration: ALTER TABLE webhook_logs ADD COLUMN decision_reason TEXT; — safe nullable ADD COLUMN
  • Manual: Send test webhook → verify in cascade webhooklogs list/show and Dashboard UI

Trello card: https://trello.com/c/69abfd22ea94baebe551922b

🤖 Generated with Claude Code

@nhopeatall
Copy link
Copy Markdown
Collaborator

nhopeatall commented Mar 7, 2026

✅ Review submitted for PR #645 — clean feature, one minor issue flagged (duplicate decision reason string in Steps 1/2 of webhook processor). See review comments for details.

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean, well-structured feature that threads decisionReason consistently through the full webhook pipeline — from processor to DB to CLI/Dashboard. Migration is safe (nullable ADD COLUMN), tests cover all decision points, and the UI changes are correct (colSpan updated, truncation with title tooltip). One minor issue worth addressing.

Code Issues

Should Fix

  • src/router/webhook-processor.ts:64 — Step 2 (filter check) reuses the identical decision reason as Step 1 (parse failure): both return 'Event unparseable or not processable'. Step 2 fires after the event was successfully parsed — it specifically means "event type not processable" (e.g., a board-level action). Using the same string makes it impossible to distinguish these two distinct cases in webhook logs, which partially defeats the purpose of this feature. Suggest something like 'Event type not processable' or 'Event filtered (type not handled)' for Step 2.

Comment thread src/router/webhook-processor.ts Outdated
if (!adapter.isProcessableEvent(event)) {
logger.debug(`Ignoring ${adapter.type} event`, { eventType: event.eventType });
return { shouldProcess: false };
return { shouldProcess: false, decisionReason: 'Event unparseable or not processable' };
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[SHOULD_FIX] This reuses the same decision reason string as Step 1 (line 58), but these are distinct decision points. Step 1 means the payload couldn't be parsed at all; Step 2 means it parsed successfully but the event type isn't processable. Using the same string makes these indistinguishable in logs.

Suggest differentiating, e.g.:

The test at line 95 would need updating too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in f267235. Step 2 now returns "Event type not processable: <eventType>" (with the actual event type interpolated), clearly distinguishing it from Step 1's "Event unparseable or not processable". Updated the corresponding test assertion as well.

@zbigniewsobiecki
Copy link
Copy Markdown
Member

@aaight address code review concerns

…bhook processor

Step 2 (event type filter) now returns "Event type not processable: <eventType>"
instead of reusing the Step 1 (parse failure) reason string, making the two
distinct decision points distinguishable in logs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aaight
Copy link
Copy Markdown
Collaborator Author

aaight commented Mar 7, 2026

Code Review Feedback Addressed

What was asked

Address the code review concern about Step 1 and Step 2 in webhook-processor.ts sharing the same decisionReason string, making them indistinguishable in logs.

Changes Made

  • src/router/webhook-processor.ts: Step 2 (event type filter) now returns "Event type not processable: <eventType>" instead of reusing Step 1's "Event unparseable or not processable", making the two decision points clearly distinguishable in logs
  • tests/unit/router/webhook-processor.test.ts: Updated the test assertion to match the new Step 2 reason string ('Event type not processable: commentCard')

Verification

  • All 18 webhook-processor tests passing ✅
  • All 10 webhookLogger tests passing ✅
  • All 15 webhookHandlers tests passing ✅
  • Biome lint passing ✅
  • TypeScript type-check passing ✅

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

LGTM — Clean, well-structured feature addition that threads decisionReason through all layers consistently. The nullable TEXT column migration is safe, types are aligned across all interfaces (ProcessRouterWebhookResult → WebhookLogOverrides → WebhookLogInput → InsertWebhookLogInput → DB), every decision branch in processRouterWebhook() provides a distinct human-readable reason, and tests cover all paths. Dashboard and CLI display are handled correctly (colSpan bump, truncation in list view, full display in detail view). No issues found.

@zbigniewsobiecki zbigniewsobiecki merged commit d80749e into dev Mar 7, 2026
6 checks passed
@zbigniewsobiecki zbigniewsobiecki deleted the feature/webhook-decision-reason-logging branch March 16, 2026 16:43
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.

3 participants