Skip to content

Conversation

@jumski
Copy link
Contributor

@jumski jumski commented Jan 14, 2026

Add Conditional Steps and Error Handling

This PR introduces comprehensive support for conditional step execution and graceful error handling in pgflow. The new features allow workflows to adapt to runtime conditions and handle failures without stopping the entire run.

Key Features

  • Pattern Matching: Control step execution with if/ifNot conditions using PostgreSQL's JSON containment operator
  • Skip Modes: Configure what happens when conditions aren't met with fail, skip, or skip-cascade options
  • Error Handling: Gracefully handle step failures with retriesExhausted: 'skip' for non-critical steps
  • Type Safety: TypeScript types reflect optional dependencies when steps might be skipped

Visual Representation

Added a new step_skipped style to the D2 theme for visualizing skipped steps in flow diagrams.

Documentation

Added comprehensive documentation with:

  • Overview of conditional execution and error handling
  • Pattern matching syntax and examples
  • Skip mode behaviors and use cases
  • Error handling best practices
  • Type safety considerations
  • Visual diagrams showing different execution paths

These features enable more resilient workflows where non-critical steps can fail without stopping the entire process.

@changeset-bot
Copy link

changeset-bot bot commented Jan 14, 2026

🦋 Changeset detected

Latest commit: 096d008

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

This PR includes changesets to release 5 packages
Name Type
@pgflow/core Minor
@pgflow/dsl Minor
pgflow Minor
@pgflow/client Minor
@pgflow/edge-worker Minor

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

Copy link
Contributor Author

jumski commented Jan 14, 2026

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.

@nx-cloud
Copy link

nx-cloud bot commented Jan 14, 2026

View your CI Pipeline Execution ↗ for commit 096d008

Command Status Duration Result
nx run edge-worker:test:integration ✅ Succeeded 3m 48s View ↗
nx affected -t verify-exports --base=origin/mai... ✅ Succeeded 3s View ↗
nx affected -t build --configuration=production... ✅ Succeeded 3s View ↗
nx affected -t lint typecheck test --parallel -... ✅ Succeeded 2m 23s View ↗
nx run cli:e2e ✅ Succeeded 3s View ↗
nx run client:e2e ✅ Succeeded 1m 9s View ↗
nx run edge-worker:e2e ✅ Succeeded 39s View ↗
nx run core:pgtap ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-23 23:47:44 UTC

@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 1d9f0cf to 8cfa234 Compare January 15, 2026 05:19
@jumski jumski force-pushed the 01-14-fix_skip-cascade_type_bug_only_skip_mode_makes_deps_optional branch from 44482fc to 12c7537 Compare January 20, 2026 09:23
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 8cfa234 to 45fa698 Compare January 20, 2026 09:23
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 45fa698 to 44a7cad Compare January 20, 2026 14:25
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 44a7cad to 117e766 Compare January 20, 2026 17:01
@jumski jumski changed the base branch from 01-14-fix_skip-cascade_type_bug_only_skip_mode_makes_deps_optional to graphite-base/594 January 21, 2026 10:18
@jumski jumski force-pushed the graphite-base/594 branch from 12c7537 to 963cad9 Compare January 21, 2026 10:18
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 117e766 to 56ec88e Compare January 21, 2026 10:18
@jumski jumski changed the base branch from graphite-base/594 to 01-05-condition January 21, 2026 10:19
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 56ec88e to 844ba94 Compare January 21, 2026 21:48
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 844ba94 to 11417a0 Compare January 21, 2026 22:15
Comment on lines 268 to 393
## Conditional Execution Options

These options control which steps execute based on input patterns and how failures are handled. See [Conditional Steps](/build/conditional-steps/) for detailed explanations and examples.

### `if`

**Type:** `ContainmentPattern<Input>`
**Default:** Not applicable (must be explicitly set)

Run the step only if input contains the specified pattern. pgflow uses PostgreSQL's `@>` containment operator for matching.

```typescript
// Root step - checks flow input
.step({
slug: 'premiumFeature',
if: { plan: 'premium' }, // Only run for premium users
}, handler)

// Dependent step - checks dependency output
.step({
slug: 'notify',
dependsOn: ['analyze'],
if: { analyze: { needsAlert: true } }, // Check analyze output
}, handler)
```

<Aside type="note">
The pattern type `ContainmentPattern<Input>` matches the input shape:
- Root steps: Pattern is checked against flow input
- Dependent steps: Pattern is checked against object `{ depSlug: depOutput, ... }`
</Aside>

### `ifNot`

**Type:** `ContainmentPattern<Input>`
**Default:** Not applicable (must be explicitly set)

Run the step only if input does NOT contain the specified pattern.

```typescript
.step({
slug: 'standardUserFlow',
ifNot: { role: 'admin' }, // Skip admin users
}, handler)
```

You can combine `if` and `ifNot` - both conditions must be satisfied:

```typescript
.step({
slug: 'targetedNotification',
if: { status: 'active' }, // Must be active
ifNot: { role: 'admin' }, // AND must not be admin
}, handler)
```

### `whenUnmet`

**Type:** `'fail' | 'skip' | 'skip-cascade'`
**Default:** `'skip'`

Controls what happens when `if` or `ifNot` condition is not met.

| Mode | Behavior |
| ---------------- | ------------------------------------------------------------------------------- |
| `'fail'` | Step fails, entire run fails |
| `'skip'` | Step marked as skipped, run continues, dependents receive `undefined` (default) |
| `'skip-cascade'` | Step AND all downstream dependents skipped, run continues |

```typescript
.step({
slug: 'enrichData',
if: { includeEnrichment: true },
whenUnmet: 'skip', // Default - could be omitted
}, handler)

.step({
slug: 'criticalPath',
if: { plan: 'premium' },
whenUnmet: 'fail', // Explicit - fail if not premium
}, handler)
```

<Aside type="tip">
The default of `'skip'` means steps with conditions automatically continue the
workflow rather than failing the entire run.
</Aside>

### `retriesExhausted`

**Type:** `'fail' | 'skip' | 'skip-cascade'`
**Default:** `'fail'`

Controls what happens when a step fails after exhausting all `maxAttempts` retry attempts.

| Mode | Behavior |
| ---------------- | --------------------------------------------------------------------- |
| `'fail'` | Step fails, entire run fails (default) |
| `'skip'` | Step marked as skipped, run continues, dependents receive `undefined` |
| `'skip-cascade'` | Step AND all downstream dependents skipped, run continues |

```typescript
.step({
slug: 'sendEmail',
maxAttempts: 3,
retriesExhausted: 'skip', // Don't fail run if email service is down
}, handler)

.step({
slug: 'criticalOperation',
maxAttempts: 5,
retriesExhausted: 'fail', // Default - fail if operation fails
}, handler)
```

<Aside type="tip">
Use `retriesExhausted: 'skip'` for non-critical steps like notifications,
analytics, or optional enrichment.
</Aside>

<Aside type="caution" title="TYPE_VIOLATION Errors">
Programming errors like returning wrong type (e.g., string instead of array
for map step) always fail the run, regardless of `retriesExhausted` setting.
These are bugs in your code, not runtime conditions. >>>>>>> 153eae64 (add
docs for conditional steps)
</Aside>
Copy link
Contributor

Choose a reason for hiding this comment

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

Complete duplication of the "Conditional Execution Options" section. Lines 268-393 are an exact duplicate of lines 142-267, which will cause confusing documentation with repeated content. The entire duplicated section should be removed.

- ## Conditional Execution Options
- 
- These options control which steps execute based on input patterns and how failures are handled. See [Conditional Steps](/build/conditional-steps/) for detailed explanations and examples.
- 
- ### `if`
- ...
- (remove all duplicate content from lines 268-393)

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 11417a0 to 7dc63bb Compare January 21, 2026 22:19
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 7dc63bb to 5e56496 Compare January 22, 2026 09:03
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 5e56496 to 208f76f Compare January 22, 2026 10:23
@jumski jumski changed the base branch from 01-05-condition to graphite-base/594 January 23, 2026 10:57
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from 208f76f to b7b1a8c Compare January 23, 2026 12:22
@jumski jumski force-pushed the graphite-base/594 branch from 963cad9 to 736e5b4 Compare January 23, 2026 12:22
@jumski jumski changed the base branch from graphite-base/594 to 01-05-condition January 23, 2026 12:22
@jumski jumski force-pushed the 01-14-add_docs_for_conditional_steps branch from b7b1a8c to 096d008 Compare January 23, 2026 23:40
@github-actions
Copy link
Contributor

🔍 Preview Deployment: Website

Deployment successful!

🔗 Preview URL: https://pr-594.pgflow.pages.dev

📝 Details:

  • Branch: 01-14-add_docs_for_conditional_steps
  • Commit: e5f117bfccb34cc5467d6f9d14d6735ee20ef684
  • View Logs

_Last updated: _

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