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

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

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit into
graphite-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

changeset-botBot commented Dec 18, 2025

Copy link
Copy Markdown

🦋 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
NameType
@workflow/corePatch
@workflow/worldPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-vercelPatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/docs-typecheckPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/nuxtPatch
@workflow/aiPatch

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

vercelBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentReviewUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorJan 15, 2026 1:44am
example-nextjs-workflow-webpackErrorErrorJan 15, 2026 1:44am
example-workflowErrorErrorJan 15, 2026 1:44am
workbench-astro-workflowErrorErrorJan 15, 2026 1:44am
workbench-express-workflowErrorErrorJan 15, 2026 1:44am
workbench-fastify-workflowErrorErrorJan 15, 2026 1:44am
workbench-hono-workflowErrorErrorJan 15, 2026 1:44am
workbench-nitro-workflowErrorErrorJan 15, 2026 1:44am
workbench-nuxt-workflowErrorErrorJan 15, 2026 1:44am
workbench-sveltekit-workflowErrorErrorJan 15, 2026 1:44am
workbench-vite-workflowErrorErrorJan 15, 2026 1:44am
workflow-docsErrorErrorJan 15, 2026 1:44am

@github-actions

github-actionsBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

🧪 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.

@pranaygppranaygp mentioned this pull request Dec 18, 2025
3 tasks
@pranaygpGraphite App

Copy link
Copy Markdown
ContributorAuthor

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/641December 18, 2025 03:51
@pranaygp
pranaygpforce-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6bCompareDecember 18, 2025 04:07
@pranaygp
pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-eventsDecember 18, 2025 04:07

@vercelvercelBot left a comment

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.

🔧 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

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 reuseawaitdrizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId,effectiveRunId));
  2. runFailedEvents (line ~849): Added:

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

    // Delete all hooks for this run to allow token reuseawaitdrizzle.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

@vercelvercelBot left a comment

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.

🔧 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 freeto 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

@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

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

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit into
graphite-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

changeset-botBot commented Dec 18, 2025

Copy link
Copy Markdown

🦋 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
NameType
@workflow/corePatch
@workflow/worldPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-vercelPatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/docs-typecheckPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/nuxtPatch
@workflow/aiPatch

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

vercelBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentReviewUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorJan 15, 2026 1:44am
example-nextjs-workflow-webpackErrorErrorJan 15, 2026 1:44am
example-workflowErrorErrorJan 15, 2026 1:44am
workbench-astro-workflowErrorErrorJan 15, 2026 1:44am
workbench-express-workflowErrorErrorJan 15, 2026 1:44am
workbench-fastify-workflowErrorErrorJan 15, 2026 1:44am
workbench-hono-workflowErrorErrorJan 15, 2026 1:44am
workbench-nitro-workflowErrorErrorJan 15, 2026 1:44am
workbench-nuxt-workflowErrorErrorJan 15, 2026 1:44am
workbench-sveltekit-workflowErrorErrorJan 15, 2026 1:44am
workbench-vite-workflowErrorErrorJan 15, 2026 1:44am
workflow-docsErrorErrorJan 15, 2026 1:44am

@github-actions

github-actionsBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

🧪 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.

@pranaygppranaygp mentioned this pull request Dec 18, 2025
3 tasks
@pranaygpGraphite App

Copy link
Copy Markdown
ContributorAuthor

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/641December 18, 2025 03:51
@pranaygp
pranaygpforce-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6bCompareDecember 18, 2025 04:07
@pranaygp
pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-eventsDecember 18, 2025 04:07

@vercelvercelBot left a comment

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.

🔧 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

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 reuseawaitdrizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId,effectiveRunId));
  2. runFailedEvents (line ~849): Added:

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

    // Delete all hooks for this run to allow token reuseawaitdrizzle.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

@vercelvercelBot left a comment

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.

🔧 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 freeto 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

@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit into
graphite-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

changeset-botBot commented Dec 18, 2025

Copy link
Copy Markdown

🦋 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
NameType
@workflow/corePatch
@workflow/worldPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-vercelPatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/docs-typecheckPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/nuxtPatch
@workflow/aiPatch

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

vercelBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentReviewUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorJan 15, 2026 1:44am
example-nextjs-workflow-webpackErrorErrorJan 15, 2026 1:44am
example-workflowErrorErrorJan 15, 2026 1:44am
workbench-astro-workflowErrorErrorJan 15, 2026 1:44am
workbench-express-workflowErrorErrorJan 15, 2026 1:44am
workbench-fastify-workflowErrorErrorJan 15, 2026 1:44am
workbench-hono-workflowErrorErrorJan 15, 2026 1:44am
workbench-nitro-workflowErrorErrorJan 15, 2026 1:44am
workbench-nuxt-workflowErrorErrorJan 15, 2026 1:44am
workbench-sveltekit-workflowErrorErrorJan 15, 2026 1:44am
workbench-vite-workflowErrorErrorJan 15, 2026 1:44am
workflow-docsErrorErrorJan 15, 2026 1:44am

@github-actions

github-actionsBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

🧪 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.

@pranaygppranaygp mentioned this pull request Dec 18, 2025
3 tasks
@pranaygpGraphite App

Copy link
Copy Markdown
ContributorAuthor

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/641December 18, 2025 03:51
@pranaygp
pranaygpforce-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6bCompareDecember 18, 2025 04:07
@pranaygp
pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-eventsDecember 18, 2025 04:07

@vercelvercelBot left a comment

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.

🔧 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

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 reuseawaitdrizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId,effectiveRunId));
  2. runFailedEvents (line ~849): Added:

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

    // Delete all hooks for this run to allow token reuseawaitdrizzle.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

@vercelvercelBot left a comment

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.

🔧 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 freeto 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

@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit into
graphite-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

changeset-botBot commented Dec 18, 2025

Copy link
Copy Markdown

🦋 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
NameType
@workflow/corePatch
@workflow/worldPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-vercelPatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/docs-typecheckPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/nuxtPatch
@workflow/aiPatch

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

vercelBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentReviewUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorJan 15, 2026 1:44am
example-nextjs-workflow-webpackErrorErrorJan 15, 2026 1:44am
example-workflowErrorErrorJan 15, 2026 1:44am
workbench-astro-workflowErrorErrorJan 15, 2026 1:44am
workbench-express-workflowErrorErrorJan 15, 2026 1:44am
workbench-fastify-workflowErrorErrorJan 15, 2026 1:44am
workbench-hono-workflowErrorErrorJan 15, 2026 1:44am
workbench-nitro-workflowErrorErrorJan 15, 2026 1:44am
workbench-nuxt-workflowErrorErrorJan 15, 2026 1:44am
workbench-sveltekit-workflowErrorErrorJan 15, 2026 1:44am
workbench-vite-workflowErrorErrorJan 15, 2026 1:44am
workflow-docsErrorErrorJan 15, 2026 1:44am

@github-actions

github-actionsBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

🧪 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.

@pranaygppranaygp mentioned this pull request Dec 18, 2025
3 tasks
@pranaygpGraphite App

Copy link
Copy Markdown
ContributorAuthor

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/641December 18, 2025 03:51
@pranaygp
pranaygpforce-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6bCompareDecember 18, 2025 04:07
@pranaygp
pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-eventsDecember 18, 2025 04:07

@vercelvercelBot left a comment

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.

🔧 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

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 reuseawaitdrizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId,effectiveRunId));
  2. runFailedEvents (line ~849): Added:

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

    // Delete all hooks for this run to allow token reuseawaitdrizzle.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

@vercelvercelBot left a comment

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.

🔧 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 freeto 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

@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

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

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit into
graphite-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

changeset-botBot commented Dec 18, 2025

Copy link
Copy Markdown

🦋 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
NameType
@workflow/corePatch
@workflow/worldPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-vercelPatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/docs-typecheckPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/nuxtPatch
@workflow/aiPatch

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

vercelBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentReviewUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorJan 15, 2026 1:44am
example-nextjs-workflow-webpackErrorErrorJan 15, 2026 1:44am
example-workflowErrorErrorJan 15, 2026 1:44am
workbench-astro-workflowErrorErrorJan 15, 2026 1:44am
workbench-express-workflowErrorErrorJan 15, 2026 1:44am
workbench-fastify-workflowErrorErrorJan 15, 2026 1:44am
workbench-hono-workflowErrorErrorJan 15, 2026 1:44am
workbench-nitro-workflowErrorErrorJan 15, 2026 1:44am
workbench-nuxt-workflowErrorErrorJan 15, 2026 1:44am
workbench-sveltekit-workflowErrorErrorJan 15, 2026 1:44am
workbench-vite-workflowErrorErrorJan 15, 2026 1:44am
workflow-docsErrorErrorJan 15, 2026 1:44am

@github-actions

github-actionsBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

🧪 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.

@pranaygppranaygp mentioned this pull request Dec 18, 2025
3 tasks
@pranaygpGraphite App

Copy link
Copy Markdown
ContributorAuthor

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/641December 18, 2025 03:51
@pranaygp
pranaygpforce-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6bCompareDecember 18, 2025 04:07
@pranaygp
pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-eventsDecember 18, 2025 04:07

@vercelvercelBot left a comment

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.

🔧 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

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 reuseawaitdrizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId,effectiveRunId));
  2. runFailedEvents (line ~849): Added:

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

    // Delete all hooks for this run to allow token reuseawaitdrizzle.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

@vercelvercelBot left a comment

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.

🔧 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 freeto 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

@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit into
graphite-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

changeset-botBot commented Dec 18, 2025

Copy link
Copy Markdown

🦋 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
NameType
@workflow/corePatch
@workflow/worldPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-vercelPatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/docs-typecheckPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/nuxtPatch
@workflow/aiPatch

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

vercelBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentReviewUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorJan 15, 2026 1:44am
example-nextjs-workflow-webpackErrorErrorJan 15, 2026 1:44am
example-workflowErrorErrorJan 15, 2026 1:44am
workbench-astro-workflowErrorErrorJan 15, 2026 1:44am
workbench-express-workflowErrorErrorJan 15, 2026 1:44am
workbench-fastify-workflowErrorErrorJan 15, 2026 1:44am
workbench-hono-workflowErrorErrorJan 15, 2026 1:44am
workbench-nitro-workflowErrorErrorJan 15, 2026 1:44am
workbench-nuxt-workflowErrorErrorJan 15, 2026 1:44am
workbench-sveltekit-workflowErrorErrorJan 15, 2026 1:44am
workbench-vite-workflowErrorErrorJan 15, 2026 1:44am
workflow-docsErrorErrorJan 15, 2026 1:44am

@github-actions

github-actionsBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

🧪 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.

@pranaygppranaygp mentioned this pull request Dec 18, 2025
3 tasks
@pranaygpGraphite App

Copy link
Copy Markdown
ContributorAuthor

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/641December 18, 2025 03:51
@pranaygp
pranaygpforce-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6bCompareDecember 18, 2025 04:07
@pranaygp
pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-eventsDecember 18, 2025 04:07

@vercelvercelBot left a comment

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.

🔧 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

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 reuseawaitdrizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId,effectiveRunId));
  2. runFailedEvents (line ~849): Added:

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

    // Delete all hooks for this run to allow token reuseawaitdrizzle.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

@vercelvercelBot left a comment

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.

🔧 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 freeto 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

@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit into
graphite-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

changeset-botBot commented Dec 18, 2025

Copy link
Copy Markdown

🦋 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
NameType
@workflow/corePatch
@workflow/worldPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-vercelPatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/docs-typecheckPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/nuxtPatch
@workflow/aiPatch

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

vercelBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentReviewUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorJan 15, 2026 1:44am
example-nextjs-workflow-webpackErrorErrorJan 15, 2026 1:44am
example-workflowErrorErrorJan 15, 2026 1:44am
workbench-astro-workflowErrorErrorJan 15, 2026 1:44am
workbench-express-workflowErrorErrorJan 15, 2026 1:44am
workbench-fastify-workflowErrorErrorJan 15, 2026 1:44am
workbench-hono-workflowErrorErrorJan 15, 2026 1:44am
workbench-nitro-workflowErrorErrorJan 15, 2026 1:44am
workbench-nuxt-workflowErrorErrorJan 15, 2026 1:44am
workbench-sveltekit-workflowErrorErrorJan 15, 2026 1:44am
workbench-vite-workflowErrorErrorJan 15, 2026 1:44am
workflow-docsErrorErrorJan 15, 2026 1:44am

@github-actions

github-actionsBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

🧪 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.

@pranaygppranaygp mentioned this pull request Dec 18, 2025
3 tasks
@pranaygpGraphite App

Copy link
Copy Markdown
ContributorAuthor

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/641December 18, 2025 03:51
@pranaygp
pranaygpforce-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6bCompareDecember 18, 2025 04:07
@pranaygp
pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-eventsDecember 18, 2025 04:07

@vercelvercelBot left a comment

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.

🔧 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

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 reuseawaitdrizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId,effectiveRunId));
  2. runFailedEvents (line ~849): Added:

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

    // Delete all hooks for this run to allow token reuseawaitdrizzle.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

@vercelvercelBot left a comment

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.

🔧 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 freeto 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

@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

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

perf: add events.createBatch() for batch event creation#641
pranaygp wants to merge 1 commit into
graphite-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

changeset-botBot commented Dec 18, 2025

Copy link
Copy Markdown

🦋 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
NameType
@workflow/corePatch
@workflow/worldPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-vercelPatch
@workflow/buildersPatch
@workflow/cliPatch
@workflow/docs-typecheckPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/web-sharedPatch
workflowPatch
@workflow/world-testingPatch
@workflow/astroPatch
@workflow/sveltekitPatch
@workflow/nuxtPatch
@workflow/aiPatch

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

vercelBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentReviewUpdated (UTC)
example-nextjs-workflow-turbopackErrorErrorJan 15, 2026 1:44am
example-nextjs-workflow-webpackErrorErrorJan 15, 2026 1:44am
example-workflowErrorErrorJan 15, 2026 1:44am
workbench-astro-workflowErrorErrorJan 15, 2026 1:44am
workbench-express-workflowErrorErrorJan 15, 2026 1:44am
workbench-fastify-workflowErrorErrorJan 15, 2026 1:44am
workbench-hono-workflowErrorErrorJan 15, 2026 1:44am
workbench-nitro-workflowErrorErrorJan 15, 2026 1:44am
workbench-nuxt-workflowErrorErrorJan 15, 2026 1:44am
workbench-sveltekit-workflowErrorErrorJan 15, 2026 1:44am
workbench-vite-workflowErrorErrorJan 15, 2026 1:44am
workflow-docsErrorErrorJan 15, 2026 1:44am

@github-actions

github-actionsBot commented Dec 18, 2025

Copy link
Copy Markdown
Contributor

🧪 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.

@pranaygppranaygp mentioned this pull request Dec 18, 2025
3 tasks
@pranaygpGraphite App

Copy link
Copy Markdown
ContributorAuthor

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/641December 18, 2025 03:51
@pranaygp
pranaygpforce-pushed the pranaygp/perf-batch-events branch from 853963d to 752bc6bCompareDecember 18, 2025 04:07
@pranaygp
pranaygp changed the base branch from graphite-base/641 to pranaygp/perf-phase-3b-atomic-eventsDecember 18, 2025 04:07

@vercelvercelBot left a comment

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.

🔧 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

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 reuseawaitdrizzle.delete(Schema.hooks).where(eq(Schema.hooks.runId,effectiveRunId));
  2. runFailedEvents (line ~849): Added:

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

    // Delete all hooks for this run to allow token reuseawaitdrizzle.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

@vercelvercelBot left a comment

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.

🔧 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 freeto 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

@pranaygp