Skip to content

clean up nested workflows and events#204

Merged
ianmacartney merged 4 commits intoian/use-convex-sizefrom
ian/cleanup-nested-workflow-and-events
Mar 3, 2026
Merged

clean up nested workflows and events#204
ianmacartney merged 4 commits intoian/use-convex-sizefrom
ian/cleanup-nested-workflow-and-events

Conversation

@ianmacartney
Copy link
Copy Markdown
Member

@ianmacartney ianmacartney commented Feb 19, 2026

TL;DR

Enhanced workflow cleanup to properly delete associated events and nested workflows.

Fixes #186

What changed?

Added logic to the cleanup mutation to handle additional resource deletion:

  • When a journal entry's step is of kind "event", the associated event is now deleted
  • When a journal entry's step is of kind "workflow" with a workflowId, a cleanup mutation is enqueued to recursively clean up the nested workflow

How to test?

  1. Create a workflow that contains event steps and nested workflow steps
  2. Execute the workflow
  3. Trigger the cleanup mutation for the parent workflow
  4. Verify that all associated events are deleted from the database
  5. Verify that cleanup is properly propagated to nested workflows

Why make this change?

This change prevents resource leaks by ensuring that when a workflow is cleaned up, all of its associated resources (events and nested workflows) are properly deleted as well. Without this change, orphaned events and nested workflows would remain in the database after the parent workflow is cleaned up.

Summary by CodeRabbit

  • Bug Fixes

    • Improved workflow cleanup to properly handle cascading deletions of associated events and nested workflows.
  • Tests

    • Added comprehensive test coverage for workflow cleanup scenarios, including nested workflows, event handling, and cascade deletion behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ian/cleanup-nested-workflow-and-events

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Member Author

ianmacartney commented Feb 19, 2026

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

Copy link
Copy Markdown
Member Author

@cursor can you write tests for this?

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Feb 19, 2026

Open in StackBlitz

npm i https://pkg.pr.new/get-convex/workflow/@convex-dev/workflow@204

commit: 32f4d45

@cursor
Copy link
Copy Markdown

cursor Bot commented Feb 19, 2026

I've written tests for the cleanup functionality and also fixed a bug I discovered. Here's what was done:

Tests Added (5 new tests in src/component/workflow.test.ts):

  • cleanup deletes associated events - Verifies that when a workflow with event steps is cleaned up, the associated events are also deleted
  • cleanup enqueues cleanup for nested workflows - Verifies that when a parent workflow is cleaned up, cleanup is properly enqueued for nested workflows via workpool
  • cleanup handles workflow step without workflowId - Verifies cleanup works for workflow steps that haven't started their nested workflow yet
  • cleanup handles event step without eventId - Verifies cleanup works for event steps that don't have an eventId set
  • cleanup with mixed event and workflow steps - Integration test verifying both event and workflow step cleanup works together

Bug Fix in src/component/workflow.ts:
Changed the event deletion condition from:

if (journalEntry.step.kind === "event") {
  await ctx.db.delete(journalEntry.step.eventId!);

to:

if (journalEntry.step.kind === "event" && journalEntry.step.eventId) {
  await ctx.db.delete(journalEntry.step.eventId);

This prevents a crash when eventId is undefined on event steps.

Open in Cursor Open in Web

cursoragent and others added 2 commits February 19, 2026 03:16
- Test cleanup deletes associated events when cleaning up workflow with event steps
- Test cleanup enqueues cleanup for nested workflows
- Test cleanup handles workflow step without workflowId
- Test cleanup handles event step without eventId
- Test cleanup with mixed event and workflow steps

Also fix bug where cleanup would crash when eventId is undefined on event steps.

Co-authored-by: Ian Macartney <ianmacartney@users.noreply.github.com>
…journal.startSteps

startSteps is a regular mutation, not an internal function.

Co-authored-by: Ian Macartney <ianmacartney@users.noreply.github.com>
Comment thread src/component/workflow.test.ts Outdated
Comment on lines +192 to +194
if (!nestedWorkflowCompleted) {
await t.mutation(api.workflow.cancel, { workflowId: nestedWorkflowId! });
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@cursor this should happen automatically as part of canceling the parent

Copy link
Copy Markdown

@cursor cursor Bot Feb 19, 2026

Choose a reason for hiding this comment

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

Updated the tests to rely on the parent workflow's cancel automatically canceling nested workflows. The tests now:

  1. Cancel only the parent workflow
  2. Verify that the nested workflow was automatically canceled (by checking runResult is defined)
  3. No longer manually cancel nested workflows separately

This better reflects the expected behavior where canceling a parent propagates cancellation to in-progress nested workflows.

Open in Cursor Open in Web

…workflows

Canceling the parent workflow automatically cancels in-progress nested workflows,
so there's no need to manually cancel them separately.

Co-authored-by: Ian Macartney <ianmacartney@users.noreply.github.com>
@ianmacartney ianmacartney mentioned this pull request Feb 19, 2026
@ianmacartney ianmacartney marked this pull request as ready for review February 19, 2026 04:30
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/component/workflow.ts (1)

362-364: Reuse a single workpool instance during cleanup.

At Line 362, getWorkpool(ctx, {}) runs inside the per-entry loop, which repeats config reads and client construction. Collect nested workflow IDs first, then enqueue with one workpool instance (and dedupe IDs).

♻️ Suggested refactor
-    for (const journalEntry of journalEntries) {
+    const nestedWorkflowIds = new Set<Id<"workflows">>();
+    for (const journalEntry of journalEntries) {
       logger.debug("Deleting journal entry", journalEntry);
       await ctx.db.delete(journalEntry._id);
       if (journalEntry.step.kind === "event" && journalEntry.step.eventId) {
         await ctx.db.delete(journalEntry.step.eventId);
       } else if (
         journalEntry.step.kind === "workflow" &&
         journalEntry.step.workflowId
       ) {
-        const workpool = await getWorkpool(ctx, {});
-        await workpool.enqueueMutation(ctx, api.workflow.cleanup, {
-          workflowId: journalEntry.step.workflowId,
-        });
+        nestedWorkflowIds.add(journalEntry.step.workflowId);
       }
     }
+    if (nestedWorkflowIds.size > 0) {
+      const workpool = await getWorkpool(ctx, {});
+      for (const nestedWorkflowId of nestedWorkflowIds) {
+        await workpool.enqueueMutation(ctx, api.workflow.cleanup, {
+          workflowId: nestedWorkflowId,
+        });
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/component/workflow.ts` around lines 362 - 364, The current code calls
getWorkpool(ctx, {}) inside the per-entry loop which rebuilds clients/config
repeatedly; instead, first collect all nested workflow IDs from
journalEntry.step.workflowId across entries (dedupe them), then call
getWorkpool(ctx, {}) once to obtain a single workpool and call
workpool.enqueueMutation(ctx, api.workflow.cleanup, { workflowId: id }) for each
unique id; update the loop to remove repeated getWorkpool calls and use the
single workpool instance for all enqueueMutation calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/component/workflow.ts`:
- Around line 362-364: The current code calls getWorkpool(ctx, {}) inside the
per-entry loop which rebuilds clients/config repeatedly; instead, first collect
all nested workflow IDs from journalEntry.step.workflowId across entries (dedupe
them), then call getWorkpool(ctx, {}) once to obtain a single workpool and call
workpool.enqueueMutation(ctx, api.workflow.cleanup, { workflowId: id }) for each
unique id; update the loop to remove repeated getWorkpool calls and use the
single workpool instance for all enqueueMutation calls.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2629473 and 32f4d45.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • src/component/workflow.test.ts
  • src/component/workflow.ts

Copy link
Copy Markdown
Contributor

@sethconvex sethconvex left a comment

Choose a reason for hiding this comment

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

If you're not worried about deleting lots of potential steps in one mutation, LGTM.

Comment thread src/component/workflow.ts
logger.debug("Deleting journal entry", journalEntry);
await ctx.db.delete(journalEntry._id);
if (journalEntry.step.kind === "event" && journalEntry.step.eventId) {
await ctx.db.delete(journalEntry.step.eventId);
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.

Is this going to be a problem when there are lots of steps with big args?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We currently limit the journal & size of all steps to be max 8MiB, including events I believe, so deletion should only consume <= 16MiB if we read everything and delete them individually. If they hit the limit on the nose we might be in trouble for the extra workflow metadata, but deleting events take less data b/c we only read as part of the delete, not fetched in addition.

@ianmacartney
Copy link
Copy Markdown
Member Author

Merged into main directly

@ianmacartney ianmacartney reopened this Mar 3, 2026
@ianmacartney ianmacartney merged commit 065ed19 into ian/use-convex-size Mar 3, 2026
5 of 6 checks passed
@ianmacartney ianmacartney deleted the ian/cleanup-nested-workflow-and-events branch March 3, 2026 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants