Skip to content

fix(table): trigger cascade race fixes, polling, workflow column flag#4499

Merged
TheodoreSpeaks merged 8 commits intostagingfrom
fix/table-trigger-followup
May 7, 2026
Merged

fix(table): trigger cascade race fixes, polling, workflow column flag#4499
TheodoreSpeaks merged 8 commits intostagingfrom
fix/table-trigger-followup

Conversation

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator

Summary

  • Fix late queued stamp overwriting worker's running/completed for the same execution — cells were getting stuck at queued forever.
  • Per-page polling in useInfiniteTableRows: refetch only pages that contain in-flight cells instead of every loaded page.
  • run_column optimistic patch on mode='incomplete' mirrors server eligibility (areOutputsFilled) so cells with leftover values from a prior cancelled/error run no longer flash queued.
  • Hide the Workflow column type behind NEXT_PUBLIC_WORKFLOW_COLUMNS_ENABLED (default false). Existing workflow groups still render.

Type of Change

  • Bug fix

Testing

Tested manually. Lint and `bun run check:api-validation:strict` pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…lready started

Stamps fire in chunks of 20 via Promise.all, so queued writes race with the
worker's markWorkflowGroupPickedUp (running). When the late queued stamp
landed second it overwrote running, and the cell looked stuck in queued for
the rest of the run. Skip the stamp when the same execution is already past
queued — the worker's authority wins.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 7, 2026 10:04pm

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 7, 2026

PR Summary

Medium Risk
Changes workflow-column execution dispatch and execution-state write guards, plus custom polling logic that updates React Query cache incrementally; mistakes could cause stuck statuses, missed updates, or extra load.

Overview
Makes POST /api/table/[tableId]/columns/run and the copilot run_column tool dispatch runWorkflowColumn in the background (no longer awaiting fan-out), updating the API contract so triggered can be null and adjusting the returned message.

Reworks useInfiniteTableRows polling to refetch only pages that contain in-flight workflow cells and merges refreshed pages while preserving row object identity to reduce re-renders.

Fixes workflow execution edge cases: optimistic run patches for runMode='incomplete' now skip rows whose outputs are already filled (areOutputsFilled), cell write logic prevents late queued stamps from overwriting running/completed for the same execution, and manual-run eligibility treats autoRun=false groups as runnable. Also hides the "Workflow" column type behind NEXT_PUBLIC_WORKFLOW_COLUMNS_ENABLED (default off).

Reviewed by Cursor Bugbot for commit c19ec14. Bugbot is set up for automated code reviews on this repo. Configure here.

…flow column flag

- Polling now refetches only pages that contain in-flight cells instead of
  every loaded page. Idle pages stay untouched while a cascade runs.
- run_column optimistic patch mirrors server eligibility on mode='incomplete':
  cells with filled outputs no longer flip to queued only to revert seconds
  later when the server returns 0 triggered.
- Hide the Workflow column type behind NEXT_PUBLIC_WORKFLOW_COLUMNS_ENABLED
  (default false). Existing workflow groups keep rendering.
@TheodoreSpeaks TheodoreSpeaks force-pushed the fix/table-trigger-followup branch from 0e0a55f to 89c758a Compare May 7, 2026 21:21
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 7, 2026

Greptile Summary

This PR addresses three distinct bugs in the table workflow-column execution path and adds a feature flag to hide the Workflow column type in the new-column dropdown.

  • cell-write.ts: Fixes a race where a late queued stamp for an already-progressed run (same executionId, status already running/completed) could overwrite the further-along state, leaving cells stuck at "queued" forever. The fix splits the isAuthoritativeNewStamp bypass into a same-run guard and a new-run guard.
  • tables.ts: Replaces the built-in refetchInterval (which refetched all loaded pages) with a manual per-page polling loop that only refetches pages containing in-flight cells. Also fixes the run_column optimistic patch for mode='incomplete' to use areOutputsFilled (matching server logic) instead of checking exec.status === 'completed', so cells with leftover values from cancelled/errored runs no longer flash queued.
  • Feature flag: NEXT_PUBLIC_WORKFLOW_COLUMNS_ENABLED (default false) hides the Workflow column type from the new-column dropdown without breaking existing workflow groups.

Confidence Score: 4/5

Safe to merge; the core race fixes in cell-write.ts are well-reasoned and the optimistic patch alignment is correct.

The cell-write and optimistic-patch changes are correct and close real bugs. The per-page polling implementation works but the queryKey reference is not memoized: every cache write from a poll tick causes a re-render, which creates a new array reference, which triggers the effect cleanup and re-creates the interval. In normal conditions polling still fires at roughly the right cadence, but the interval resets after each update rather than running continuously.

apps/sim/hooks/queries/tables.ts — the queryKey memoization and the missing useMemo import are the only items that need attention.

Important Files Changed

Filename Overview
apps/sim/hooks/queries/tables.ts Replaces React Query's built-in refetchInterval with a manual per-page polling loop. The queryKey array is recreated on every render (not memoized), so the setInterval resets after every cache update triggered by the poll itself.
apps/sim/lib/table/cell-write.ts Fixes the race where a late queued stamp for the same run could overwrite a further-along running/completed state. Logic correctly handles all combinations of same/different executionId and current status.
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/new-column-dropdown/new-column-dropdown.tsx Gates the Workflow column type behind NEXT_PUBLIC_WORKFLOW_COLUMNS_ENABLED flag; correctly evaluates the flag at module load time so existing workflow groups continue to render.
apps/sim/lib/core/config/env.ts Adds NEXT_PUBLIC_WORKFLOW_COLUMNS_ENABLED as an optional boolean env var; consistent with existing feature-flag patterns in this file.
apps/sim/lib/core/config/feature-flags.ts Exports isWorkflowColumnsEnabledClient using getEnv — correct client-safe access pattern; defaults to false.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Scheduler stamps queued] --> B{Same executionId as current?}
    B -- No --> C[isNewQueuedStamp = true bypass stale-worker guard]
    B -- Yes --> D{current.status == pending?}
    D -- Yes --> E[Allow queued to write over pending]
    D -- No --> F[Skip - late stamp cell already progressed]
    C --> G[Write queued to DB]
    E --> G
    F --> H[Return skipped]

    subgraph Per-page polling loop
    I[setInterval tick] --> J{queryClient.isMutating > 0?}
    J -- Yes --> K[Skip tick]
    J -- No --> L[Walk pages find dirty ones]
    L --> M{Any dirty pages?}
    M -- No --> N[No-op]
    M -- Yes --> O[fetchTableRows for each dirty page]
    O --> P[setQueryData splice fresh page]
    end
Loading

Reviews (1): Last reviewed commit: "fix(table): per-page polling, optimistic..." | Re-trigger Greptile

Comment thread apps/sim/hooks/queries/tables.ts Outdated
Comment thread apps/sim/hooks/queries/tables.ts Outdated
Comment thread apps/sim/hooks/queries/tables.ts Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b23ba1b. Configure here.

if (runMode === 'incomplete') {
const group = groupsById.get(groupId)
if (group && areOutputsFilled(group, r)) continue
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optimistic patch diverges from server eligibility check

Medium Severity

The client optimistic patch for mode='incomplete' skips cells solely based on areOutputsFilled(group, r), but the server's classifyEligibility skips only when status === 'completed' && areOutputsFilled(group, row) (the completedAndFilled variable). For cancelled or error cells with leftover output values, the client incorrectly skips them (no "queued" feedback) even though the server considers them eligible and will run them. The comment claims it "mirrors server eligibility" but it's missing the exec?.status === 'completed' condition the server requires.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b23ba1b. Configure here.

Comment thread apps/sim/hooks/queries/tables.ts
Large fan-outs (thousands of rows) issue sequential trigger.dev HTTP calls
inside scheduleRunsForRows.batchEnqueue. Awaiting that loop held the HTTP
response (and the AI tool span) open for ~5 min on a 6k-row table — the user
saw an 11-min "running" because the tool didn't return until every job had
been enqueued. Run the dispatcher in the background and return immediately;
contract response now reports `triggered: null` since the count isn't known
synchronously.
Each poll tick brings back a fresh page from the server with all-new row
objects, even though most rows haven't changed. setQueryData was replacing
the whole page reference, which made every memoized <DataRow> in the page
re-render every 1.5s. Now we shallow-compare each fresh row against the
cached one and reuse the cached reference when nothing changed; only rows
whose data or exec status actually flipped re-render.
Refactoring eligibility into classifyEligibility split the runnable answer
into two reasons: 'eligible' (deps satisfied) and 'manual-bypass' (autoRun=
false group on a manual run, deps don't apply). isGroupEligible only treated
'eligible' as runnable, so a manual "Run all rows" on a single autoRun=false
group filtered every row out and returned triggered: 0. Cells flashed
queued from the optimistic patch then went empty when the refetch landed.
@TheodoreSpeaks TheodoreSpeaks merged commit 81845ae into staging May 7, 2026
14 checks passed
@TheodoreSpeaks TheodoreSpeaks deleted the fix/table-trigger-followup branch May 7, 2026 22: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