Skip to content

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

Merged
ianmacartney merged 5 commits intomainfrom
ian/unstable-args-option
Apr 15, 2026
Merged

allow passing { unstableArgs } to step.run*#225
ianmacartney merged 5 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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

Adds unstableArgs: boolean to StepRequest and RunOptions, threads it through scheduling/run paths (default false), and changes StepExecutor.completeMessage to optionally exclude args from journal-vs-message comparisons when unstableArgs is true. Tests added for propagation and comparison behavior.

Changes

Cohort / File(s) Summary
Step runtime
src/client/step.ts
Added exported unstableArgs: boolean to StepRequest. StepExecutor.completeMessage conditionally omits args from the journal-entry vs message comparison when message.unstableArgs is true; updated mismatch error to include both JSONs on separate lines.
Workflow context / API
src/client/workflowContext.ts
Added optional unstableArgs?: boolean to exported RunOptions; relaxed previous inline scheduling type constraint in public signatures and allowed inline via opts. Propagated unstableArgs into created StepRequest (unstableArgs ?? false or false for some helpers). Runtime validation still rejects invalid inline combinations and disallows inline for action function types.
Tests
src/client/stepContext.test.ts
Added unit tests for StepExecutor.completeMessage covering unstableArgs true/false and name-mismatch case; added integration-style test to assert StepRequest.unstableArgs propagation from runQuery/runMutation/runAction/runWorkflow calls.
Imports / minor
src/client/...
Small typing and import adjustments to accept inline?: boolean in public method signatures and thread new option through internal call sites.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • reeceyang
  • sethconvex

Poem

🐇 I twitch my nose and give a cheer,
a tiny flag has hopped in here.
"unstableArgs" says, "skip those args today,"
the journal checks will look another way.
Hop on, defaults steady — bounce away!

🚥 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
Title check ✅ Passed The title 'allow passing { unstableArgs } to step.run*' accurately describes the main change: adding an unstableArgs option parameter to workflow step execution APIs.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 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: cad4d2b

@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

Comment thread src/client/workflowContext.ts
@ianmacartney ianmacartney force-pushed the ian/unstable-args-option branch 2 times, most recently from 2bbf4ae to 6a680b6 Compare April 7, 2026 03:52
Copy link
Copy Markdown
Member Author

@cursor review

@cursor
Copy link
Copy Markdown

cursor Bot commented Apr 8, 2026

Skipping Bugbot: Bugbot is disabled for this repository. Visit the Bugbot dashboard to update your settings.

@ianmacartney ianmacartney requested a review from reeceyang April 10, 2026 03:24
@ianmacartney ianmacartney force-pushed the ian/unstable-args-option branch from 6a680b6 to 6ecd002 Compare April 10, 2026 09:00
@ianmacartney ianmacartney force-pushed the ian/unstable-args-option branch from 6ecd002 to 5037738 Compare April 11, 2026 04:44
Copy link
Copy Markdown

@reeceyang reeceyang left a comment

Choose a reason for hiding this comment

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

looks good!

* work pool. Cannot be combined with `runAfter` or `runAt`.
*/
inline?: boolean;
inline: true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

are the changes to inline related to unstableArgs?

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.

Only that by adding another arg I realized that "inline" was allowed to be passed to actions and things that didn't actually support it, and you could pass runAfter and inline at the same time which doesn't make sense. So I updated the types to add unstableArgs & fix inline handling

@ianmacartney ianmacartney merged commit b23ac0d into main Apr 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants