Skip to content

Conversation

@amikofalvy
Copy link
Collaborator

Summary

This PR adds tracking for trigger invocations that fail validation, storing error details for debugging and monitoring purposes.

Changes

Schema

  • Added 'rejected' status to TriggerInvocationStatusEnum
  • Added errorCode column (integer, nullable) to store HTTP status code
  • Added respondedAt column (timestamp, nullable) to track when response was sent

Backend

  • Refactored processWebhook to create rejected invocations at each validation failure point:
    • Trigger not found (404)
    • Trigger disabled (404)
    • Authentication failure (401/403)
    • Signature verification failure (403/500)
    • Payload validation failure (400)
    • Transform failure (422)
  • Webhooks route now returns invocationId in error responses
  • Added safeParseJson helper for graceful JSON parsing

UI

  • Added "Rejected" status to invocations filter dropdown
  • Added outline badge variant for rejected status
  • Error column now displays error code alongside error message
  • Expanded details show response time (calculated from respondedAt - createdAt)

Tests

Added 8 new tests covering rejected invocation tracking for all failure scenarios.

Migration

Migration 0010_eminent_ronan.sql adds the new columns to the trigger_invocations table.

- Add 'rejected' status to TriggerInvocationStatusEnum
- Add errorCode (int) and respondedAt (timestamp) columns to triggerInvocations
- Refactor processWebhook to create rejected invocations at each validation failure point:
  - Trigger not found (404)
  - Trigger disabled (404)
  - Auth failure (401/403)
  - Signature verification failure (403/500)
  - Payload validation failure (400)
  - Transform failure (422)
- Update webhooks route to return invocationId in error responses
- Update UI to show rejected status, error codes, and response time
- Add comprehensive tests for rejected invocation tracking
@changeset-bot
Copy link

changeset-bot bot commented Jan 23, 2026

⚠️ No Changeset found

Latest commit: d3c6b0e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Jan 23, 2026

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

Project Deployment Review Updated (UTC)
agents-api Ready Ready Preview, Comment Jan 24, 2026 1:38am
agents-manage-ui Ready Ready Preview, Comment Jan 24, 2026 1:38am
1 Skipped Deployment
Project Deployment Review Updated (UTC)
agents-docs Skipped Skipped Jan 24, 2026 1:38am

Request Review

@claude
Copy link

claude bot commented Jan 23, 2026

Claude finished @amikofalvy's task —— View job


Code Review: Track Rejected Trigger Invocations

Review Checklist:

  • Read repository guidelines (AGENTS.md)
  • Analyze schema changes and migration
  • Review core TriggerService implementation
  • Examine webhook route changes
  • Review UI components and consistency
  • Analyze test coverage
  • Check for security, performance, and architectural concerns
  • Provide comprehensive feedback

Summary

This is a well-architected feature that adds comprehensive tracking for rejected trigger invocations. The implementation demonstrates solid engineering practices with proper error handling, database design, and test coverage. However, there are several areas for improvement around API consistency, performance optimization, and system design considerations.

Critical Issues

🚨 API Contract Inconsistency (High Priority)

Problem: The webhook API now sometimes returns invocationId in error responses, breaking the established OpenAPI contract.

Current OpenAPI spec:

400: { schema: z.object({ error: z.string(), validationErrors: z.array(z.string()).optional() }) }
404: { schema: z.object({ error: z.string() }) }

Actual response: { error: string, invocationId?: string, validationErrors?: string[] }

Impact: This is a breaking change that could cause client applications to fail or behave unexpectedly.

Solution: Update the OpenAPI specs in webhooks.ts:59-110 to include invocationId as an optional field across all error response schemas.

🔍 Race Condition Risk (Medium Priority)

Location: TriggerService.ts:87

const respondedAt = new Date().toISOString(); // Generated before DB write
await createTriggerInvocation(runDbClient)({ ... });

Problem: The respondedAt timestamp is generated before the database write, creating a small window where the timestamp doesn't reflect when the response was actually sent.

Solution: Consider setting respondedAt in the webhook route after the response is sent, or use database NOW() for more accurate timing.

Architectural & Design Feedback

Excellent Patterns

  • Comprehensive error tracking: All failure points are properly instrumented
  • Backward compatibility: Schema changes are non-breaking with nullable columns
  • Atomic operations: Each failure path creates exactly one rejected invocation
  • Test coverage: 8 dedicated tests cover all rejection scenarios

🎯 System Design Considerations

1. Performance & Scalability

The current approach creates database records for every failed request, including malicious traffic:

// This runs for ANY failed request, even obvious attacks
const invocationId = await createRejectedInvocation({ ... });

Consider:

  • Rate limiting before database writes for obvious failures (malformed JSON, missing auth)
  • Batch processing for high-volume rejection scenarios
  • TTL/cleanup strategy for rejected invocations to prevent unbounded growth

2. Observability Enhancement

The current error categorization could be more granular:

Suggestion: Add rejectionReason enum alongside errorCode:

rejectionReason: 'TRIGGER_NOT_FOUND' | 'TRIGGER_DISABLED' | 'AUTH_MISSING' | 'AUTH_INVALID' | 'SIGNATURE_INVALID' | 'PAYLOAD_INVALID' | 'TRANSFORM_FAILED'

This would enable better metrics and alerting.

3. JSON Parsing Resilience

The safeParseJson helper silently returns {} for invalid JSON:

function safeParseJson(rawBody: string): Record<string, unknown> {
  try {
    return JSON.parse(rawBody) as Record<string, unknown>;
  } catch {
    return {}; // Information loss
  }
}

Issue: This loses information about what the client actually sent.

Better approach:

function safeParseJson(rawBody: string): { parsed: Record<string, unknown>; raw?: string } {
  try {
    return { parsed: JSON.parse(rawBody) };
  } catch {
    return { parsed: {}, raw: rawBody.slice(0, 1000) }; // Preserve original for debugging
  }
}

Technical Implementation Review

Database Schema

The migration is well-designed:

  • Non-breaking nullable columns
  • Proper indexing maintained
  • Consistent with existing patterns

UI Implementation

  • Proper status filtering includes "Rejected"
  • Error display shows both code and message
  • Response time calculation is correct
  • Consistent with existing UI patterns

Type Safety

Schema updates properly flow through the type system:

  • TriggerInvocationStatusEnum includes 'rejected'
  • Database schema matches validation schemas
  • API types are consistent

Minor Improvements

1. Error Message Consistency

Different failure modes use inconsistent error messages:

"Trigger ${triggerId} not found"  // Template literal
"Trigger is disabled"             // Static string

Suggestion: Standardize error message format for better user experience.

2. Security Consideration

The 404 responses for both "not found" and "disabled" are good for security (no information leakage), but consider if disabled triggers should have different internal tracking.

3. Code Organization

The createRejectedInvocation helper is well-designed but could be extracted to a shared utility if rejection tracking expands to other domains.

Recommendations

Immediate Actions

  1. Fix API contract - Update OpenAPI specs to include invocationId in error responses
  2. Consider race condition - Review timestamp generation timing

Future Enhancements

  1. Add rejection reason categorization for better observability
  2. Implement cleanup/archival strategy for old rejected invocations
  3. Consider rate limiting for obvious attack patterns
  4. Enhance JSON parsing to preserve raw data for debugging

Monitoring & Alerting

This feature enables powerful observability. Consider setting up alerts for:

  • High rejection rates by reason
  • Unusual authentication failure patterns
  • Payload validation trends

Overall Assessment

This is a production-ready feature that significantly improves the platform's observability and debugging capabilities. The implementation follows sound engineering principles and maintains system consistency. The identified issues are addressable and don't block the core functionality.

Recommendation: Approve with minor fixes (primarily the API contract update).


- Replace custom error response schemas with commonGetErrorResponses
- Use createApiError to throw proper Problem Details format errors
- Include invocationId and validationErrors as extensions
- Update tests to use 'detail' field instead of 'error' for message assertions
- Update OpenAPI snapshot to reflect the new error response schema

This ensures the webhook API contract is consistent with the rest of the
API and follows the application/problem+json specification.
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