Skip to content

perf: add events.createBatch() for batch event creation#641

Closed
pranaygp wants to merge 1 commit intographite-base/641from
pranaygp/perf-batch-events
Closed

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit intographite-base/641from
pranaygp/perf-batch-events

Conversation

@pranaygp
Copy link
Copy Markdown
Contributor

🤖 Generated with Claude Code

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

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Dec 18, 2025

🦋 Changeset detected

Latest commit: 7ad380a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@workflow/core Patch
@workflow/world Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/world-vercel Patch
@workflow/builders Patch
@workflow/cli Patch
@workflow/docs-typecheck Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/web-shared Patch
workflow Patch
@workflow/world-testing Patch
@workflow/astro Patch
@workflow/sveltekit Patch
@workflow/nuxt Patch
@workflow/ai Patch

Not sure what this means? Click here to learn what changesets are.

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

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Dec 18, 2025

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

Project Deployment Review Updated (UTC)
example-nextjs-workflow-turbopack Error Error Jan 15, 2026 1:44am
example-nextjs-workflow-webpack Error Error Jan 15, 2026 1:44am
example-workflow Error Error Jan 15, 2026 1:44am
workbench-astro-workflow Error Error Jan 15, 2026 1:44am
workbench-express-workflow Error Error Jan 15, 2026 1:44am
workbench-fastify-workflow Error Error Jan 15, 2026 1:44am
workbench-hono-workflow Error Error Jan 15, 2026 1:44am
workbench-nitro-workflow Error Error Jan 15, 2026 1:44am
workbench-nuxt-workflow Error Error Jan 15, 2026 1:44am
workbench-sveltekit-workflow Error Error Jan 15, 2026 1:44am
workbench-vite-workflow Error Error Jan 15, 2026 1:44am
workflow-docs Error Error Jan 15, 2026 1:44am

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 18, 2025

🧪 E2E Test Results

No test result files found.


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: failure

Check the workflow run for details.

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@pranaygp pranaygp changed the base branch from pranaygp/perf-phase-3b-atomic-events to graphite-base/641 December 18, 2025 03:51
@pranaygp pranaygp force-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6b Compare December 18, 2025 04:07
@pranaygp pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-events December 18, 2025 04:07
Copy link
Copy Markdown
Contributor

@vercel vercel Bot left a comment

Choose a reason for hiding this comment

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

🔧 Build Fix:

The CreateEventRequest type is used in the createWorkflowRunEventBatch function but is not imported from the @workflow/world package. This causes a TypeScript compilation error because the type is undefined in this file.

Fix on Vercel

@pranaygp pranaygp changed the base branch from pranaygp/perf-phase-3b-atomic-events to graphite-base/641 December 23, 2025 04:58
@pranaygp pranaygp force-pushed the pranaygp/perf-batch-events branch from f15434b to 32f21aa Compare December 24, 2025 04:19
@pranaygp pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-events December 24, 2025 04:20
Comment on lines +766 to +841
// Update run status for run_completed events
if (runCompletedEvents.length > 0) {
const completedData = (runCompletedEvents[0] as any).eventData as {
output?: any;
};
await drizzle
.update(Schema.runs)
.set({
status: 'completed',
output: completedData.output as SerializedContent | undefined,
completedAt: now,
updatedAt: now,
})
.where(eq(Schema.runs.runId, effectiveRunId));
}

// Update run status for run_failed events
if (runFailedEvents.length > 0) {
const failedData = (runFailedEvents[0] as any).eventData as {
error: any;
errorCode?: string;
};
const errorMessage =
typeof failedData.error === 'string'
? failedData.error
: (failedData.error?.message ?? 'Unknown error');
// Store structured error as JSON for deserializeRunError to parse
const errorJson = JSON.stringify({
message: errorMessage,
stack: failedData.error?.stack,
code: failedData.errorCode,
});
await drizzle
.update(Schema.runs)
.set({
status: 'failed',
error: errorJson,
completedAt: now,
updatedAt: now,
})
.where(eq(Schema.runs.runId, effectiveRunId));
}

// Update run status for run_cancelled events
if (runCancelledEvents.length > 0) {
await drizzle
.update(Schema.runs)
.set({
status: 'cancelled',
completedAt: now,
updatedAt: now,
})
.where(eq(Schema.runs.runId, effectiveRunId));
}

// Update run status for run_paused events
if (runPausedEvents.length > 0) {
await drizzle
.update(Schema.runs)
.set({
status: 'paused',
updatedAt: now,
})
.where(eq(Schema.runs.runId, effectiveRunId));
}

// Update run status for run_resumed events
if (runResumedEvents.length > 0) {
await drizzle
.update(Schema.runs)
.set({
status: 'running',
updatedAt: now,
})
.where(eq(Schema.runs.runId, effectiveRunId));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The createBatch method is missing the hook cleanup logic that the create method performs when processing terminal run events (run_completed, run_failed, run_cancelled). This causes inconsistent behavior and prevents hook tokens from being reused.

View Details
📝 Patch Details
diff --git a/packages/world-postgres/src/storage.ts b/packages/world-postgres/src/storage.ts
index 4d56f53..2075c10 100644
--- a/packages/world-postgres/src/storage.ts
+++ b/packages/world-postgres/src/storage.ts
@@ -777,6 +777,10 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] {
             updatedAt: now,
           })
           .where(eq(Schema.runs.runId, effectiveRunId));
+        // Delete all hooks for this run to allow token reuse
+        await drizzle
+          .delete(Schema.hooks)
+          .where(eq(Schema.hooks.runId, effectiveRunId));
       }
 
       // Update run status for run_failed events
@@ -804,6 +808,10 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] {
             updatedAt: now,
           })
           .where(eq(Schema.runs.runId, effectiveRunId));
+        // Delete all hooks for this run to allow token reuse
+        await drizzle
+          .delete(Schema.hooks)
+          .where(eq(Schema.hooks.runId, effectiveRunId));
       }
 
       // Update run status for run_cancelled events
@@ -816,6 +824,10 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] {
             updatedAt: now,
           })
           .where(eq(Schema.runs.runId, effectiveRunId));
+        // Delete all hooks for this run to allow token reuse
+        await drizzle
+          .delete(Schema.hooks)
+          .where(eq(Schema.hooks.runId, effectiveRunId));
       }
 
       // Update run status for run_paused events

Analysis

Missing hook cleanup in createBatch() for terminal run events

What fails: The createBatch() method in packages/world-postgres/src/storage.ts fails to delete hooks when processing terminal run events (run_completed, run_failed, run_cancelled), unlike the create() method which properly cleans up hooks. This causes hook tokens to never be released for reuse, leading to potential token exhaustion in systems processing many workflow runs.

How to reproduce:

  1. Call createBatch() with events including a hook_created event
  2. Process a batch with a terminal event (run_completed, run_failed, or run_cancelled)
  3. Check the database hooks table - hooks will still exist for that run
  4. Attempt to create and complete another workflow run with the same hook token
  5. Observe that the old hooks are not cleaned up, preventing token reuse

Result: The hooks table retains entries for completed runs indefinitely. Comparing to the create() method behavior at lines 416-419, 451-454, and 471-474, these hooks should be deleted after status updates, but are not in createBatch().

Expected: Both create() and createBatch() should consistently delete hooks when terminal run events occur (lines 766-841 in createBatch should include hook deletion after each terminal status update, matching the pattern in create()).

Root cause: Missing drizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId, effectiveRunId)) statements after the runCompletedEvents, runFailedEvents, and runCancelledEvents status update blocks in createBatch().

Implementation note: Added hook deletion logic to match the exact pattern used in the create() method, ensuring consistent behavior across both APIs and allowing hook tokens to be reused for future workflow runs.

const runData = (eventData as any).eventData as {
deploymentId: string;
workflowName: string;
input: any[];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing hook cleanup logic in createBatch() method for terminal run events (run_completed, run_failed, run_cancelled)

View Details
📝 Patch Details
diff --git a/packages/world-postgres/src/storage.ts b/packages/world-postgres/src/storage.ts
index e904510..96a9370 100644
--- a/packages/world-postgres/src/storage.ts
+++ b/packages/world-postgres/src/storage.ts
@@ -843,6 +843,10 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] {
             updatedAt: now,
           })
           .where(eq(Schema.runs.runId, effectiveRunId));
+        // Delete all hooks for this run to allow token reuse
+        await drizzle
+          .delete(Schema.hooks)
+          .where(eq(Schema.hooks.runId, effectiveRunId));
       }
 
       // Update run status for run_failed events
@@ -870,6 +874,10 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] {
             updatedAt: now,
           })
           .where(eq(Schema.runs.runId, effectiveRunId));
+        // Delete all hooks for this run to allow token reuse
+        await drizzle
+          .delete(Schema.hooks)
+          .where(eq(Schema.hooks.runId, effectiveRunId));
       }
 
       // Update run status for run_cancelled events
@@ -882,6 +890,10 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] {
             updatedAt: now,
           })
           .where(eq(Schema.runs.runId, effectiveRunId));
+        // Delete all hooks for this run to allow token reuse
+        await drizzle
+          .delete(Schema.hooks)
+          .where(eq(Schema.hooks.runId, effectiveRunId));
       }
 
       // Update run status for run_paused events

Analysis

Issue Description

The create() method in the createEventsStorage function properly cleans up hooks when handling terminal run events to allow hook token reuse. However, the createBatch() method was missing this cleanup logic for all three terminal event types.

Root Cause

In the create() method (lines ~428-502), each terminal event handler includes hook cleanup:

  • run_completed (line ~446): Deletes hooks via drizzle.delete(Schema.hooks).where(...)
  • run_failed (line ~479): Deletes hooks via drizzle.delete(Schema.hooks).where(...)
  • run_cancelled (line ~502): Deletes hooks via drizzle.delete(Schema.hooks).where(...)

In the createBatch() method (lines 838-883), the corresponding event handlers update the run status but do not delete hooks:

  • runCompletedEvents (line 838-846): Missing hook cleanup
  • runFailedEvents (line 849-872): Missing hook cleanup
  • runCancelledEvents (line 875-883): Missing hook cleanup

This inconsistency causes hook tokens to never be released for reuse in batch operations, while they are properly released in single event operations.

Fix Applied

Added hook cleanup logic to all three terminal event handlers in createBatch():

  1. runCompletedEvents (line ~838): Added:

    // Delete all hooks for this run to allow token reuse
    await drizzle
      .delete(Schema.hooks)
      .where(eq(Schema.hooks.runId, effectiveRunId));
  2. runFailedEvents (line ~849): Added:

    // Delete all hooks for this run to allow token reuse
    await drizzle
      .delete(Schema.hooks)
      .where(eq(Schema.hooks.runId, effectiveRunId));
  3. runCancelledEvents (line ~875): Added:

    // Delete all hooks for this run to allow token reuse
    await drizzle
      .delete(Schema.hooks)
      .where(eq(Schema.hooks.runId, effectiveRunId));

These additions ensure consistency between the create() and createBatch() methods, allowing hook tokens to be properly reused regardless of whether events are created individually or in batches.

Impact

  • Prevents hook token accumulation in the database
  • Enables proper token reuse across multiple workflow runs
  • Ensures consistent behavior between single and batch event operations
  • Maintains data integrity by cleaning up resources when runs reach terminal states
Fix on Vercel

};
}

export async function createWorkflowRunEventBatch(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing CreateEventRequest type import in packages/world-vercel/src/events.ts

View Details
📝 Patch Details
diff --git a/packages/world-vercel/src/events.ts b/packages/world-vercel/src/events.ts
index 137f5af..9ac7cbe 100644
--- a/packages/world-vercel/src/events.ts
+++ b/packages/world-vercel/src/events.ts
@@ -1,6 +1,7 @@
 import {
   type AnyEventRequest,
   type CreateEventParams,
+  type CreateEventRequest,
   type Event,
   type EventResult,
   EventSchema,

Analysis

The function createWorkflowRunEventBatch at line 198 of packages/world-vercel/src/events.ts uses CreateEventRequest[] as a parameter type, but the CreateEventRequest type was not imported from the @workflow/world package. This would cause a TypeScript compilation error: "Cannot find name 'CreateEventRequest'".

The fix adds type CreateEventRequest to the import statement from @workflow/world, which is where the type is exported (packages/world/src/events.ts:270). The CreateEventRequest type is defined as Exclude<AnyEventRequest, RunCreatedEventRequest> and is used to represent event requests that can be created after a workflow run has already been initiated.

This is a straightforward type import fix that resolves the compilation error without changing any logic or functionality.

Fix on Vercel

Copy link
Copy Markdown
Contributor

@vercel vercel Bot left a comment

Choose a reason for hiding this comment

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

🔧 Build Fix:

The file src/events.ts contains two TypeScript compilation errors: CreateEventRequest is not defined on line 200, and EventResultSchema is not defined on line 221. These are likely due to missing imports or incorrect variable names.

Fix on Vercel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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