Skip to content

allow passing { unstableArgs } to step.run*#225

Open
ianmacartney wants to merge 3 commits intomainfrom
ian/unstable-args-option
Open

allow passing { unstableArgs } to step.run*#225
ianmacartney wants to merge 3 commits intomainfrom
ian/unstable-args-option

Conversation

@ianmacartney
Copy link
Copy Markdown
Member

@ianmacartney ianmacartney commented Mar 26, 2026

TL;DR

Added unstableArgs option to workflow step execution that allows journal validation to skip argument comparison during replay.

Why make this change?

This enables workflows to handle scenarios where step arguments are non-deterministic and may change between executions, such as when arguments are derived from stack traces or other runtime-dependent data. Without this option, such workflows would fail during replay due to argument mismatches in the journal validation.

Summary by CodeRabbit

  • New Features
    • Added an optional runtime setting to control whether step argument differences are enforced during journal replay; defaults preserve existing behavior and is propagated through run/schedule calls.
  • Chores
    • Made step-replay validation conditional on that setting.
    • Improved mismatch error output with clearer, multi-line messages.
  • Tests
    • Added tests covering propagation of the new setting and validation behaviors.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Warning

Rate limit exceeded

@ianmacartney has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 26 minutes and 16 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 26 minutes and 16 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b17b0bbf-c606-40db-9a41-09e682913b2c

📥 Commits

Reviewing files that changed from the base of the PR and between 05a93e2 and 2bbf4ae.

📒 Files selected for processing (3)
  • src/client/step.ts
  • src/client/stepContext.test.ts
  • src/client/workflowContext.ts
📝 Walkthrough

Walkthrough

Adds an unstableArgs flag to StepRequest and RunOptions, threads it through workflow step scheduling (default false), and makes StepExecutor.completeMessage optionally skip args when comparing replayed journal entries. Tests validate propagation and behavior.

Changes

Cohort / File(s) Summary
Step runtime
src/client/step.ts
Added unstableArgs: boolean to StepRequest. StepExecutor.completeMessage conditionally omits args from the journal-vs-message JSON comparison when message.unstableArgs is true; expanded mismatch error to show both JSON strings.
Workflow context / API
src/client/workflowContext.ts
Added optional unstableArgs?: boolean to exported RunOptions. createWorkflowCtx and internal runFunction now propagate unstableArgs into scheduled StepRequest (using unstableArgs ?? false), and runtime validation disallows inline for actions. sleep/awaitEvent/some scheduling paths explicitly set unstableArgs: false.
Tests
src/client/stepContext.test.ts
Added unit tests for StepExecutor.completeMessage behavior with/without unstableArgs, ensured name mismatches still fail, and integration-style tests asserting enqueued StepRequest messages carry the expected unstableArgs flag.

Sequence Diagram(s)

(omitted — changes are localized and do not introduce multi-component control-flow requiring a sequence diagram)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • sethconvex
  • reeceyang

Poem

🐇 I twitch my nose and sing a tune,
a tiny flag arrived this noon.
When `unstableArgs` says "let go",
we skip the args and let things flow.
Defaults hold steady — hop on, we're tuned!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: adding support for passing an unstableArgs option to workflow step execution methods.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ian/unstable-args-option

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

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

@ianmacartney ianmacartney changed the title allow passing { unstableArgs } allow passing { unstableArgs } to step.run Mar 26, 2026
@ianmacartney ianmacartney changed the title allow passing { unstableArgs } to step.run allow passing { unstableArgs } to step.run* Mar 26, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 26, 2026

Open in StackBlitz

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

commit: 2bbf4ae

@ianmacartney ianmacartney marked this pull request as ready for review March 26, 2026 05:21
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/client/workflowContext.ts (1)

163-177: 🛠️ Refactor suggestion | 🟠 Major

Destructure unstableArgs from opts to avoid leaking it into schedulerOptions.

Unlike runFunction (line 222) which correctly destructures unstableArgs out before spreading to schedulerOptions, this function only extracts name. This means unstableArgs will be included in schedulerOptions, which is inconsistent and could cause issues if schedulerOptions is validated downstream.

♻️ Proposed fix
     runWorkflow: async (workflow, args, opts?) => {
-      const { name, ...schedulerOptions } = opts ?? {};
+      const { name, unstableArgs, ...schedulerOptions } = opts ?? {};
       return run(sender, {
         name: name ?? safeFunctionName(workflow),
         target: {
           kind: "workflow",
           function: workflow,
           args,
         },
         retry: undefined,
         inline: false,
-        unstableArgs: opts?.unstableArgs ?? false,
+        unstableArgs: unstableArgs ?? false,
         schedulerOptions,
       });
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/workflowContext.ts` around lines 163 - 177, The runWorkflow
implementation leaks unstableArgs into schedulerOptions because it only
destructures name from opts; update runWorkflow to also destructure unstableArgs
from opts (e.g., const { name, unstableArgs, ...schedulerOptions } = opts ?? {})
before passing schedulerOptions to run so unstableArgs is not spread into
schedulerOptions and instead use unstableArgs in the unstableArgs field
(matching runFunction behavior); edit the runWorkflow function where run is
called (symbols: runWorkflow, opts, unstableArgs, schedulerOptions, run,
safeFunctionName, sender) to apply this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/client/workflowContext.ts`:
- Around line 163-177: The runWorkflow implementation leaks unstableArgs into
schedulerOptions because it only destructures name from opts; update runWorkflow
to also destructure unstableArgs from opts (e.g., const { name, unstableArgs,
...schedulerOptions } = opts ?? {}) before passing schedulerOptions to run so
unstableArgs is not spread into schedulerOptions and instead use unstableArgs in
the unstableArgs field (matching runFunction behavior); edit the runWorkflow
function where run is called (symbols: runWorkflow, opts, unstableArgs,
schedulerOptions, run, safeFunctionName, sender) to apply this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 642092c8-79c5-4c3f-99c9-9f0ef093562c

📥 Commits

Reviewing files that changed from the base of the PR and between 19f6d93 and a731cc5.

📒 Files selected for processing (2)
  • src/client/step.ts
  • src/client/workflowContext.ts

@ianmacartney ianmacartney force-pushed the ian/unstable-args-option branch from a731cc5 to 9f25441 Compare March 26, 2026 07:59
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 (2)
src/client/workflowContext.ts (1)

163-175: Harden runWorkflow against unsupported inline option.

At Line 164, opts.inline can still flow into schedulerOptions for runWorkflow. Since inline execution is not supported for workflows, explicitly reject or strip it to avoid silent misuse.

Proposed fix
-    runWorkflow: async (workflow, args, opts?) => {
-      const { name, unstableArgs, ...schedulerOptions } = opts ?? {};
+    runWorkflow: async (workflow, args, opts?) => {
+      const { name, unstableArgs, inline, ...schedulerOptions } = opts ?? {};
+      if (inline !== undefined) {
+        throw new Error("`inline` is only supported for runQuery/runMutation.");
+      }
       return run(sender, {
         name: name ?? safeFunctionName(workflow),
         target: {
           kind: "workflow",
           function: workflow,
           args,
         },
         retry: undefined,
         inline: false,
         unstableArgs: unstableArgs ?? false,
         schedulerOptions,
       });
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/workflowContext.ts` around lines 163 - 175, The runWorkflow
implementation allows opts.inline to leak into schedulerOptions which is
unsupported; in the runWorkflow function detect if opts?.inline is set and
either throw a clear error (e.g., "inline execution not supported for
workflows") or explicitly remove/ignore the inline property before building
schedulerOptions—update the destructuring around const { name, unstableArgs,
...schedulerOptions } = opts ?? {} to grab inline (const { name, unstableArgs,
inline, ...schedulerOptions } = opts ?? {}) and then either if (inline) throw
new Error(...) or ensure schedulerOptions does not contain inline before calling
run so workflows cannot be started with inline execution.
src/client/stepContext.test.ts (1)

460-470: Make the drain loop less brittle.

The fixed loop bound (8) can become a hidden hang point when this test is extended. Prefer a named expected count and assert it explicitly.

Proposed fix
   test("unstableArgs passes through and defaults correctly", async () => {
     const channel = new BaseChannel<StepRequest>(0);
     const ctx = createWorkflowCtx("wf-test" as any, channel);

     const calls: StepRequest[] = [];
+    const expectedCalls = 8;
     const drain = async () => {
-      for (let i = 0; i < 8; i++) {
+      for (let i = 0; i < expectedCalls; i++) {
         const msg = await channel.get();
         calls.push(msg);
         msg.resolve({ kind: "success", returnValue: null });
       }
     };
@@
     await Promise.all([
@@
       drain(),
     ]);

+    expect(calls).toHaveLength(expectedCalls);
     expect(calls[0].unstableArgs).toBe(true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/stepContext.test.ts` around lines 460 - 470, The drain loop in the
"unstableArgs passes through and defaults correctly" test is brittle because it
uses a hardcoded 8; replace the magic number with a named constant (e.g., const
expectedCount = X) and use that constant in the drain loop (for or while based
on calls.length < expectedCount) to drive reads from BaseChannel<StepRequest>,
then assert at the end that calls.length === expectedCount to make the test
explicit and self-documenting; update references in the test to use
expectedCount instead of 8 and keep the msg.resolve logic unchanged.
🤖 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/client/stepContext.test.ts`:
- Around line 460-470: The drain loop in the "unstableArgs passes through and
defaults correctly" test is brittle because it uses a hardcoded 8; replace the
magic number with a named constant (e.g., const expectedCount = X) and use that
constant in the drain loop (for or while based on calls.length < expectedCount)
to drive reads from BaseChannel<StepRequest>, then assert at the end that
calls.length === expectedCount to make the test explicit and self-documenting;
update references in the test to use expectedCount instead of 8 and keep the
msg.resolve logic unchanged.

In `@src/client/workflowContext.ts`:
- Around line 163-175: The runWorkflow implementation allows opts.inline to leak
into schedulerOptions which is unsupported; in the runWorkflow function detect
if opts?.inline is set and either throw a clear error (e.g., "inline execution
not supported for workflows") or explicitly remove/ignore the inline property
before building schedulerOptions—update the destructuring around const { name,
unstableArgs, ...schedulerOptions } = opts ?? {} to grab inline (const { name,
unstableArgs, inline, ...schedulerOptions } = opts ?? {}) and then either if
(inline) throw new Error(...) or ensure schedulerOptions does not contain inline
before calling run so workflows cannot be started with inline execution.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82225fd9-117a-4803-8302-83f83fe210b8

📥 Commits

Reviewing files that changed from the base of the PR and between a731cc5 and 9f25441.

📒 Files selected for processing (3)
  • src/client/step.ts
  • src/client/stepContext.test.ts
  • src/client/workflowContext.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/client/step.ts

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/client/workflowContext.ts (1)

44-60: Type system allows inline + scheduler combinations that should be caught at compile time.

The intersection RunOptions & { inline?: boolean } preserves the union structure of SchedulerOptions, meaning TypeScript permits { inline: true, runAt: 123 } or { inline: true, runAfter: undefined } at compile time. The runtime guard using the in operator catches these at execution, but compile-time validation would be clearer and safer.

To improve this, consider:

  • Restructure the type to use a discriminated union that makes inline and scheduler options mutually exclusive at the type level, or
  • Add explicit type tests validating the compile-time contract, and enable the skipped tests in example/convex/inlineTest.test.ts to ensure runtime behavior is covered in CI.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/workflowContext.ts` around lines 44 - 60, The current signatures
for runQuery and runMutation use OptionalRestArgs<RunOptions & { inline?:
boolean }, ...> which allows invalid combinations like { inline: true, runAt:
123 } at compile time; change the types so RunOptions is a discriminated union
(e.g. RunOptions = { inline: true } | ({ inline?: false } & SchedulerOptions))
and update the OptionalRestArgs usage in runQuery and runMutation to use this
new union (referencing RunOptions, SchedulerOptions, OptionalRestArgs, runQuery,
runMutation) so inline and scheduling options are mutually exclusive at the type
level; also add/enable the compile-time tests in
example/convex/inlineTest.test.ts to validate the contract in CI.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client/workflowContext.ts`:
- Around line 23-30: The unstableArgs flag currently weakens replay identity to
only name/kind in step comparison (see unstableArgs and the comparison logic in
src/client/step.ts), which lets a refactor that preserves a custom name
accidentally reuse a journal entry for the wrong target; update the
replay/keying logic to include the operation's specific target identity (e.g.,
include functionType for runQuery/runMutation/runAction and the workflow
identifier for runWorkflow) when unstableArgs is true so the stored key remains
unique, or alternatively short-term add a validation that rejects unstableArgs
when a custom name is explicitly provided (check callers runQuery, runMutation,
runAction, runWorkflow and the comparison code in step.ts) and surface a clear
error instructing the user to remove the name or disable unstableArgs.

---

Nitpick comments:
In `@src/client/workflowContext.ts`:
- Around line 44-60: The current signatures for runQuery and runMutation use
OptionalRestArgs<RunOptions & { inline?: boolean }, ...> which allows invalid
combinations like { inline: true, runAt: 123 } at compile time; change the types
so RunOptions is a discriminated union (e.g. RunOptions = { inline: true } | ({
inline?: false } & SchedulerOptions)) and update the OptionalRestArgs usage in
runQuery and runMutation to use this new union (referencing RunOptions,
SchedulerOptions, OptionalRestArgs, runQuery, runMutation) so inline and
scheduling options are mutually exclusive at the type level; also add/enable the
compile-time tests in example/convex/inlineTest.test.ts to validate the contract
in CI.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 089f2d3a-f071-404b-b0f5-bd4115ab8285

📥 Commits

Reviewing files that changed from the base of the PR and between 9f25441 and 05a93e2.

📒 Files selected for processing (1)
  • src/client/workflowContext.ts

@ianmacartney ianmacartney force-pushed the ian/unstable-args-option branch from 05a93e2 to 2bbf4ae Compare April 2, 2026 00:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant