Skip to content

feat(workflow-executor): workflow executor package (rebased)#1525

Closed
matthv wants to merge 100 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feat/prd-214-workflow-executor-rebased
Closed

feat(workflow-executor): workflow executor package (rebased)#1525
matthv wants to merge 100 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feat/prd-214-workflow-executor-rebased

Conversation

@matthv
Copy link
Copy Markdown
Member

@matthv matthv commented Apr 1, 2026

Summary

Rebased version of #1493 onto current main (was 76 commits behind).

  • Complete @forestadmin/workflow-executor package: step executors, HTTP server, RunStore (in-memory + Sequelize), adapters, graceful shutdown
  • AI-powered step execution: condition, read-record, update-record, trigger-action, load-related-record, MCP
  • buildDatabaseExecutor / buildInMemoryExecutor factory functions
  • Example setup with Docker + PostgreSQL
  • 427 tests passing

Replaces #1493 (protected branch prevented force-push after rebase).

Test plan

  • yarn workspace @forestadmin/workflow-executor test — 427 tests pass
  • yarn workspace @forestadmin/workflow-executor lint — clean
  • yarn workspace @forestadmin/ai-proxy build — compiles after rebase conflict resolution

🤖 Generated with Claude Code

Note

Add workflow-executor package with AI-driven step executors and HTTP server

  • Introduces a new @forestadmin/workflow-executor package that polls for pending workflow steps, executes them via AI-backed executors, and exposes a Koa HTTP server for run access and triggering.
  • Adds step executor implementations for condition, read-record, update-record, trigger-action, load-related-record, and mcp step types, each supporting automatic execution or user-confirmation flows with persistence via RunStore.
  • Adds InMemoryStore and DatabaseStore (Sequelize + Umzug migrations) run store backends, exposed through buildInMemoryExecutor and buildDatabaseExecutor factory functions.
  • Refactors ai-proxy to use a ToolProvider interface, adding ForestIntegrationClient for Zendesk tools, BraveToolProvider, and a createToolProviders factory that replaces the previous MCP-only mcpConfigs with a unified toolConfigs map.
  • Extends the chart rendering pipeline (renderChart on datasource, collection, and decorators) to accept and forward an optional parameters record, parsed from request query/body by a new QueryStringParser.parseChartParameters method.
  • Risk: mcpConfigs is renamed to toolConfigs across route args and public interfaces; consumers passing mcpConfigs directly will break.
📊 Macroscope summarized 2ea7e95. 55 files reviewed, 10 issues evaluated, 1 issue filtered, 2 comments posted

🗂️ Filtered Issues

packages/workflow-executor/src/adapters/agent-client-agent-port.ts — 0 comments posted, 2 evaluated, 1 filtered
  • line 98: The condition limit !== null on line 98 does not handle undefined. If limit is omitted from the query (making it undefined), undefined !== null evaluates to true, causing { pagination: { size: undefined, number: 1 } } to be passed to the API. The check should use loose equality limit != null to correctly handle both null and undefined. [ Failed validation ]

arnaud-moncel and others added 30 commits March 18, 2026 10:17
# @forestadmin/datasource-toolkit [1.53.0](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-toolkit@1.52.0...@forestadmin/datasource-toolkit@1.53.0) (2026-03-18)

### Features

* **api chart:** allow customizing api chart with query or body parameters ([#1491](#1491)) ([a7237f2](a7237f2))
## @forestadmin/ai-proxy [1.6.2](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/ai-proxy@1.6.1...@forestadmin/ai-proxy@1.6.2) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
# @forestadmin/datasource-customizer [1.69.0](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-customizer@1.68.2...@forestadmin/datasource-customizer@1.69.0) (2026-03-18)

### Features

* **api chart:** allow customizing api chart with query or body parameters ([#1491](#1491)) ([a7237f2](a7237f2))

### Dependencies

* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/datasource-dummy [1.1.66](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-dummy@1.1.65...@forestadmin/datasource-dummy@1.1.66) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.0
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/datasource-mongoose [1.13.3](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-mongoose@1.13.2...@forestadmin/datasource-mongoose@1.13.3) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/datasource-sequelize [1.13.6](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-sequelize@1.13.5...@forestadmin/datasource-sequelize@1.13.6) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/datasource-sql [1.17.8](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-sql@1.17.7...@forestadmin/datasource-sql@1.17.8) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-sequelize:** upgraded to 1.13.6
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/forestadmin-client [1.37.18](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/forestadmin-client@1.37.17...@forestadmin/forestadmin-client@1.37.18) (2026-03-18)

### Dependencies

* **@forestadmin/ai-proxy:** upgraded to 1.6.2
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/plugin-aws-s3 [1.5.10](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/plugin-aws-s3@1.5.9...@forestadmin/plugin-aws-s3@1.5.10) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
* **@forestadmin/datasource-customizer:** upgraded to 1.69.0
## @forestadmin/plugin-export-advanced [1.1.40](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/plugin-export-advanced@1.1.39...@forestadmin/plugin-export-advanced@1.1.40) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.0
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/plugin-flattener [1.4.24](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/plugin-flattener@1.4.23...@forestadmin/plugin-flattener@1.4.24) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
* **@forestadmin/datasource-customizer:** upgraded to 1.69.0
## @forestadmin/agent-client [1.4.14](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/agent-client@1.4.13...@forestadmin/agent-client@1.4.14) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
* **@forestadmin/forestadmin-client:** upgraded to 1.37.18
## @forestadmin/datasource-replica [1.8.4](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-replica@1.8.3...@forestadmin/datasource-replica@1.8.4) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.0
* **@forestadmin/datasource-sequelize:** upgraded to 1.13.6
* **@forestadmin/datasource-sql:** upgraded to 1.17.8
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/datasource-mongo [1.6.7](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-mongo@1.6.6...@forestadmin/datasource-mongo@1.6.7) (2026-03-18)

### Dependencies

* **@forestadmin/datasource-mongoose:** upgraded to 1.13.3
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/mcp-server [1.8.9](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/mcp-server@1.8.8...@forestadmin/mcp-server@1.8.9) (2026-03-18)

### Dependencies

* **@forestadmin/agent-client:** upgraded to 1.4.14
* **@forestadmin/forestadmin-client:** upgraded to 1.37.18
# @forestadmin/agent [1.75.0](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/agent@1.74.1...@forestadmin/agent@1.75.0) (2026-03-18)

### Features

* **api chart:** allow customizing api chart with query or body parameters ([#1491](#1491)) ([a7237f2](a7237f2))

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.0
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
* **@forestadmin/forestadmin-client:** upgraded to 1.37.18
* **@forestadmin/mcp-server:** upgraded to 1.8.9
* **@forestadmin/datasource-sql:** upgraded to 1.17.8
## @forestadmin/agent-testing [1.0.23](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/agent-testing@1.0.22...@forestadmin/agent-testing@1.0.23) (2026-03-18)

### Dependencies

* **@forestadmin/agent-client:** upgraded to 1.4.14
* **@forestadmin/datasource-customizer:** upgraded to 1.69.0
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
* **@forestadmin/forestadmin-client:** upgraded to 1.37.18
* **@forestadmin/agent:** upgraded to 1.75.0
* **@forestadmin/datasource-sql:** upgraded to 1.17.8
## @forestadmin/forest-cloud [1.12.98](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/forest-cloud@1.12.97...@forestadmin/forest-cloud@1.12.98) (2026-03-18)

### Dependencies

* **@forestadmin/agent:** upgraded to 1.75.0
* **@forestadmin/datasource-customizer:** upgraded to 1.69.0
* **@forestadmin/datasource-mongo:** upgraded to 1.6.7
* **@forestadmin/datasource-mongoose:** upgraded to 1.13.3
* **@forestadmin/datasource-sequelize:** upgraded to 1.13.6
* **@forestadmin/datasource-sql:** upgraded to 1.17.8
* **@forestadmin/datasource-toolkit:** upgraded to 1.53.0
## @forestadmin/datasource-customizer [1.69.1](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-customizer@1.69.0...@forestadmin/datasource-customizer@1.69.1) (2026-03-31)

### Bug Fixes

* **capabilities:** fix aggregation validation on computed field ([#1519](#1519)) ([79056f7](79056f7))
## @forestadmin/datasource-dummy [1.1.67](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-dummy@1.1.66...@forestadmin/datasource-dummy@1.1.67) (2026-03-31)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.1
## @forestadmin/plugin-aws-s3 [1.5.11](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/plugin-aws-s3@1.5.10...@forestadmin/plugin-aws-s3@1.5.11) (2026-03-31)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.1
## @forestadmin/plugin-export-advanced [1.1.41](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/plugin-export-advanced@1.1.40...@forestadmin/plugin-export-advanced@1.1.41) (2026-03-31)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.1
## @forestadmin/plugin-flattener [1.4.25](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/plugin-flattener@1.4.24...@forestadmin/plugin-flattener@1.4.25) (2026-03-31)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.1
## @forestadmin/datasource-replica [1.8.5](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/datasource-replica@1.8.4...@forestadmin/datasource-replica@1.8.5) (2026-03-31)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.1
## @forestadmin/agent [1.75.1](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/agent@1.75.0...@forestadmin/agent@1.75.1) (2026-03-31)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.1
## @forestadmin/agent-testing [1.0.24](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/agent-testing@1.0.23...@forestadmin/agent-testing@1.0.24) (2026-03-31)

### Dependencies

* **@forestadmin/datasource-customizer:** upgraded to 1.69.1
* **@forestadmin/agent:** upgraded to 1.75.1
## @forestadmin/forest-cloud [1.12.99](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/forest-cloud@1.12.98...@forestadmin/forest-cloud@1.12.99) (2026-03-31)

### Dependencies

* **@forestadmin/agent:** upgraded to 1.75.1
* **@forestadmin/datasource-customizer:** upgraded to 1.69.1
- Bump sequelize ^6.37.8 (SQL injection CVE)
- Bump @modelcontextprotocol/sdk ^1.28.0 (fixes transitive hono, @hono/node-server, express-rate-limit)
- Bump typedoc ^0.28.18 (fixes transitive markdown-it)
- Bump forest-cli 5.3.9 (ships fixed sequelize)
- Re-resolve @aws-sdk/xml-builder to 3.972.16 (ships fixed fast-xml-parser 5.5.8)
- Remove 6 stale resolutions now covered by parent package bumps

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
alban bertolini and others added 23 commits April 1, 2026 15:22
…premature deps, add smoke test

- Rewrite CLAUDE.md with project overview and architecture principles, remove changelog
- Remove unused dependencies (ai-proxy, sequelize, zod) per YAGNI
- Add smoke test so CI passes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… document system architecture

- Lint now covers src and test directories
- Replace require() with import, use stronger assertion (toHaveLength)
- Add System Architecture section describing Front/Orchestrator/Executor/Agent
- Mark Architecture Principles as planned (not yet implemented)
- Remove redundant test/.gitkeep
- Make index.ts a valid module with export {}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…erver (#1504)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: alban bertolini <albanb@forestadmin.com>
…ain (#1512)

Co-authored-by: alban bertolini <albanb@forestadmin.com>
- Fix ai-proxy McpClient API changes (tools → loadTools, closeConnections → dispose)
- Merge ai-proxy router imports (BraveToolProvider + validateAiConfigurations)
- Update internal dependency versions to match main

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear bot commented Apr 1, 2026

@qltysh
Copy link
Copy Markdown

qltysh bot commented Apr 1, 2026

16 new issues

Tool Category Rule Count
qlty Structure Function with high complexity (count = 14): createWorkflowExecutor 7
qlty Structure Function with many returns (count = 4): getAiConfiguration 5
qlty Structure Deeply nested control flow (level = 4) 2
qlty Duplication Found 16 lines of similar code in 2 locations (mass = 92) 2

@matthv matthv changed the base branch from main to feat/prd-214-setup-workflow-executor-package April 1, 2026 14:09
@matthv matthv closed this Apr 1, 2026
@matthv matthv deleted the feat/prd-214-workflow-executor-rebased branch April 1, 2026 14:11
pendingStepExecutions: '/liana/v1/workflow-step-executions/pending',
pendingStepExecutionForRun: (runId: string) =>
`/liana/v1/workflow-step-executions/pending?runId=${encodeURIComponent(runId)}`,
updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${runId}/complete`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium adapters/forest-server-workflow-port.ts:14

When runId contains URL-reserved characters (like /, ?, or #), ROUTES.updateStepExecution produces a malformed path because the parameter is not encoded. Unlike ROUTES.pendingStepExecutionForRun which correctly uses encodeURIComponent, the unencoded runId in line 14 causes incorrect routing or 404 errors when the value is interpolated into the path.

-  updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${runId}/complete`,
+  updateStepExecution: (runId: string) => `/liana/v1/workflow-step-executions/${encodeURIComponent(runId)}/complete`,
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/forest-server-workflow-port.ts around line 14:

When `runId` contains URL-reserved characters (like `/`, `?`, or `#`), `ROUTES.updateStepExecution` produces a malformed path because the parameter is not encoded. Unlike `ROUTES.pendingStepExecutionForRun` which correctly uses `encodeURIComponent`, the unencoded `runId` in line 14 causes incorrect routing or 404 errors when the value is interpolated into the path.

Evidence trail:
packages/workflow-executor/src/adapters/forest-server-workflow-port.ts lines 12-14 at REVIEWED_COMMIT:
- Line 12-13: `pendingStepExecutionForRun` uses `encodeURIComponent(runId)`
- Line 14: `updateStepExecution` does NOT use `encodeURIComponent(runId)`

};
}

function createWorkflowExecutor(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low src/build-workflow-executor.ts:77

The stopTimeoutMs option is accepted in ExecutorOptions and passed through buildCommonDependencies, but createWorkflowExecutor never receives it. The force exit timeout on line 115 always uses the hardcoded FORCE_EXIT_DELAY_MS (5000ms), making the user-configured value ineffective.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/build-workflow-executor.ts around line 77:

The `stopTimeoutMs` option is accepted in `ExecutorOptions` and passed through `buildCommonDependencies`, but `createWorkflowExecutor` never receives it. The force exit timeout on line 115 always uses the hardcoded `FORCE_EXIT_DELAY_MS` (5000ms), making the user-configured value ineffective.

Evidence trail:
packages/workflow-executor/src/build-workflow-executor.ts lines 20, 37, 70, 73, 108-111, 145, 165 at REVIEWED_COMMIT. Key evidence: `stopTimeoutMs` defined in ExecutorOptions (line 37), included in buildCommonDependencies return (line 70), but createWorkflowExecutor signature only accepts (runner, server, logger) (line 73), and uses hardcoded FORCE_EXIT_DELAY_MS=5000 (lines 20, 108-111).

return promise;
}

private async doExecuteStep(step: PendingStepExecution, key: string): Promise<void> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High src/runner.ts:236

The finally block deletes the step key from inFlightSteps before updateStepExecution reports the outcome. During this window, a concurrent runPollCycle can fetch the same pending step, see it's not in-flight, and spawn a duplicate execution. Move the deletion to after the outcome is successfully reported.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/runner.ts around line 236:

The `finally` block deletes the step key from `inFlightSteps` before `updateStepExecution` reports the outcome. During this window, a concurrent `runPollCycle` can fetch the same pending step, see it's not in-flight, and spawn a duplicate execution. Move the deletion to after the outcome is successfully reported.

Evidence trail:
packages/workflow-executor/src/runner.ts lines 237-268: `doExecuteStep` method shows `finally` block at line 253 with `this.inFlightSteps.delete(key)` executing before `updateStepExecution` call at line 256. packages/workflow-executor/src/runner.ts lines 200-206: `runPollCycle` filters steps using `!this.inFlightSteps.has(Runner.stepKey(s))` check.

user: StepUser,
): Promise<RecordData[]> {
const client = this.createClient(user);
const relatedSchema = this.resolveSchema(relation);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low adapters/agent-client-agent-port.ts:92

In getRelatedData, resolveSchema(relation) passes the relation name (e.g., "orders") to the schema cache, but relation names often differ from their target collection names. When the lookup fails, it silently falls back to primaryKeyFields: ['id'] on line 155. This causes extractRecordId on line 104 to extract wrong primary key values when the actual related collection has a composite key or differently-named primary key field, producing RecordData with incorrect recordId values.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/agent-client-agent-port.ts around line 92:

In `getRelatedData`, `resolveSchema(relation)` passes the relation name (e.g., "orders") to the schema cache, but relation names often differ from their target collection names. When the lookup fails, it silently falls back to `primaryKeyFields: ['id']` on line 155. This causes `extractRecordId` on line 104 to extract wrong primary key values when the actual related collection has a composite key or differently-named primary key field, producing `RecordData` with incorrect `recordId` values.

Evidence trail:
packages/workflow-executor/src/adapters/agent-client-agent-port.ts:87-106 (getRelatedData method, line 92 calls resolveSchema(relation)), packages/workflow-executor/src/adapters/agent-client-agent-port.ts:148-157 (resolveSchema method with fallback to primaryKeyFields: ['id']), packages/workflow-executor/src/schema-cache.ts:15,26 (cache is keyed by collectionName), packages/workflow-executor/src/types/record.ts:11-13 (FieldSchema has relatedCollectionName property that should be used), packages/workflow-executor/test/adapters/agent-client-agent-port.test.ts:55-65 (test setup shows 'orders' collection with composite key ['tenantId', 'orderId'])

Comment on lines +38 to +41
export interface ReadRecordStepExecutionData extends BaseStepExecutionData {
type: 'read-record';
executionParams: { fields: FieldRef[] };
executionResult: { fields: FieldReadResult[] };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low types/step-execution-data.ts:38

ReadRecordStepExecutionData marks executionResult as required, but every other step type has executionResult?: (optional). This breaks the incremental data-building pattern used elsewhere—code that creates a ReadRecordStepExecutionData before execution completes will fail TypeScript compilation. Consider adding ? to make it optional like the other step types.

-  executionResult: { fields: FieldReadResult[] };
+  executionResult?: { fields: FieldReadResult[] };
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/types/step-execution-data.ts around lines 38-41:

`ReadRecordStepExecutionData` marks `executionResult` as required, but every other step type has `executionResult?:` (optional). This breaks the incremental data-building pattern used elsewhere—code that creates a `ReadRecordStepExecutionData` before execution completes will fail TypeScript compilation. Consider adding `?` to make it optional like the other step types.

Evidence trail:
packages/workflow-executor/src/types/step-execution-data.ts at REVIEWED_COMMIT:
- Line 40: `executionResult: { fields: FieldReadResult[] };` (required, no `?`)
- Line 16: `executionResult?: { answer: string };` (optional)
- Line 49: `executionResult?: { updatedValues: Record<string, unknown> } | { skipped: true };` (optional)
- Line 72: `executionResult?: { success: true; actionResult: unknown } | { skipped: true };` (optional)
- Line 93: `executionResult?: ...` (optional)
- Line 103: `executionResult?: Record<string, unknown>;` (optional)
- Line 125: `executionResult?: { relation: RelationRef; record: RecordRef } | { skipped: true };` (optional)

// NOTE: The Zod schema's .min(0).max(maxIndex) shapes the tool prompt only — it is NOT
// validated against the AI response. This guard is the sole runtime enforcement.
if (recordIndex < 0 || recordIndex > maxIndex) {
throw new InvalidAIResponseError(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Low executors/load-related-record-step-executor.ts:442

The runtime validation in selectBestRecordIndex only checks range, not integer type. If the AI returns a float like 1.5, it passes the range check but candidates[1.5] returns undefined (JavaScript arrays only accept integer indices), causing a TypeError when callers access .recordId on the result.

-    if (recordIndex < 0 || recordIndex > maxIndex) {
+    if (!Number.isInteger(recordIndex) || recordIndex < 0 || recordIndex > maxIndex) {
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/executors/load-related-record-step-executor.ts around line 442:

The runtime validation in `selectBestRecordIndex` only checks range, not integer type. If the AI returns a float like `1.5`, it passes the range check but `candidates[1.5]` returns `undefined` (JavaScript arrays only accept integer indices), causing a TypeError when callers access `.recordId` on the result.

Evidence trail:
packages/workflow-executor/src/executors/load-related-record-step-executor.ts lines 410-419 (Zod schema with .int()), lines 440-445 (comment stating Zod is not validated + runtime guard without integer check), lines 233-236 (caller using returned index for array access), lines 203-210 (preRecordedArgs path showing integer check pattern that's missing from AI path), lines 447-454 (toRecordRef function that would receive undefined).

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.

6 participants