Skip to content

fix(terminal): correct error/cancel block status in logs panel#4372

Merged
waleedlatif1 merged 3 commits intostagingfrom
waleedlatif1/exec-state-bugs
May 1, 2026
Merged

fix(terminal): correct error/cancel block status in logs panel#4372
waleedlatif1 merged 3 commits intostagingfrom
waleedlatif1/exec-state-bugs

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

@waleedlatif1 waleedlatif1 commented May 1, 2026

Summary

Three bugs in the workflow editor's terminal/logs panel where block status diverged from the engine's truth on error paths.

Bugs

  1. Errored block shown as "canceled" — a block hit via an error edge that itself throws ended up canceled instead of errored.
  2. Upstream blocks stuck on "Running" — after a run ends, blocks like Start / KnowledgeBase kept showing "Running" until a hard refresh.
  3. Phantom "Run Error" pseudo-row — the failing block showed "canceled" while a separate synthetic "Run Error" row carried the real message.

Fixes

  • Fix B (addConsoleErrorEntry, bug 1): when a running placeholder exists for (blockId, executionId), route through updateConsoleErrorEntry instead of creating a duplicate entry. Aligns SSE add mode with update mode for the same key. The duplicate is what cancelRunningEntries was sweeping into a "canceled" state.
  • Fix C (reconcileFinalBlockLogs, bug 2): terminal SSE events (execution:error / execution:cancelled) now carry finalBlockLogs — the server-authoritative snapshot. On the client, any still-running entry is reconciled against that snapshot. Recovers correctness on network drop, server timeout/abort, and reconnect-resume paths where individual block:* events may not have reached the client (Redis 1-hour replay buffer can't recover events that were never written there).
  • Fix D (addExecutionErrorConsoleEntry, bug 3): cross-check useTerminalConsoleStore for entries with error set scoped to the executionId before emitting the synthetic "Run Error" row. Suppresses the phantom row when the failing block already carries the message inline.

Architectural cleanup

  • handleExecutionErrorConsole / handleExecutionCancelledConsole now take a typed ExecutionConsoleDeps object instead of stacking positional deps. Matches the existing createBlockEventHandlers(config, deps) precedent in the same file.

Why not Fix A / Fix E (considered, dropped)

Earlier drafts included an "error-aware cancelRunningEntries" (Fix A) and a promise-tracker drain at the SSE terminal boundary (Fix E). Reverse-tracing the production code paths showed Fix A was guarding a state ({ isRunning: true, error: <set> }) that doesn't naturally arise — onBlockStarted is the only writer of isRunning: true and it sets error: undefined. Fix E targeted a race that doesn't exist in the inline route callbacks (they're async with no internal await, so controller.enqueue(block:*) always fires before the terminal enqueue regardless of void discarding). Both reverted to keep the change set surgical.

Test plan

  • 12 new tests in workflow-execution-utils.test.ts cover Fix B/C/D and the deps-object refactor (placeholder reuse, finalBlockLogs reconciliation no-op on happy path, phantom-row suppression when block carries error inline, etc.).
  • Existing terminal console store tests (4) preserved — cancelRunningEntries behavior unchanged.
  • Centralized terminal-console.mock.ts in @sim/testing so future tests can stub the store without boilerplate.
  • Manual: Start → Function2 (syntax error) -[error]→ Function1 (syntax error) — both blocks render errored (red), no "Cancelled".
  • Manual: Start → KnowledgeBase (empty KB) → Function (throws)Start and KnowledgeBase reach a terminal state without refresh.
  • Manual: Function block fetching HTML and JSON.parse-ing — failing block shows the JSON-parse error inline; no "Run Error" row unless it's a true pre-execution validation failure.
  • Manual reconnect: kill the SSE stream mid-run, wait for server, refresh — final state reconciles correctly.

Three bugs in the workflow editor's terminal/logs panel where block
status diverged from the engine's truth on error paths:

1. **Errored block shown as "canceled"** — when the SSE 'add' mode
   produced a duplicate entry on block error and `cancelRunningEntries`
   then swept the original placeholder.
2. **Upstream blocks stuck on "Running"** — terminal events arrived
   before the engine's last block events under reconnect/timeout, so
   the live panel never received the per-block terminal state.
3. **Phantom "Run Error" pseudo-row** — the failing block rendered as
   "canceled" while a synthetic row carried the real error text.

Fixes:

- **Fix B** (`addConsoleErrorEntry`): when a running placeholder exists
  for `(blockId, executionId)`, route through `updateConsoleErrorEntry`
  instead of creating a second entry. Aligns 'add' mode with the
  existing 'update' mode behavior.
- **Fix C** (`reconcileFinalBlockLogs`): terminal SSE events now carry
  `finalBlockLogs` (server-authoritative snapshot). On
  execution:error / execution:cancelled, reconcile any still-running
  entries with their server-side terminal state. Recovers correctness
  on network drop, server timeout/abort, and reconnect-resume paths
  where individual block:* events may not have reached the client.
- **Fix D** (`addExecutionErrorConsoleEntry`): cross-check
  `useTerminalConsoleStore` for entries with `error` set scoped to the
  executionId before emitting the synthetic "Run Error" row. Suppresses
  the phantom row when the failing block already carries the message.
- **Signature refactor**: `handleExecutionErrorConsole` /
  `handleExecutionCancelledConsole` now take a typed
  `ExecutionConsoleDeps` object instead of stacking positional deps —
  matches the existing `createBlockEventHandlers(config, deps)`
  precedent in the same file.

Tests:

- 12 tests in `workflow-execution-utils.test.ts` covering Fix B/C/D and
  the deps-object signature refactor.
- Centralized terminal-console store mock in `@sim/testing` so future
  tests can stub the store without per-file boilerplate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 1, 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 1, 2026 2:33am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 1, 2026

PR Summary

Medium Risk
Touches SSE execution event payloads and terminal-console reconciliation logic; mistakes could regress run-status rendering or console entry lifecycle on error/cancel/reconnect paths, but changes are localized to UI logging/stream events.

Overview
Fixes terminal/logs panel correctness on error/cancel paths by sending a server-authoritative finalBlockLogs snapshot with execution:error and execution:cancelled SSE events and using it client-side to reconcile any still-isRunning entries before sweeping remaining rows to canceled.

Also prevents duplicate/phantom error rows: block:error now updates an existing running placeholder instead of adding a second entry, and the synthetic execution-level "Run Error" entry is suppressed when a block-level error already exists in the console for the same execution. Execution-level console handlers are refactored to take a typed ExecutionConsoleDeps object, and new Vitest coverage plus a shared terminalConsoleMock are added to exercise these cases.

Reviewed by Cursor Bugbot for commit ddcab29. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 1, 2026

Greptile Summary

This PR fixes three terminal/logs panel bugs: errored blocks showing as "canceled" (Fix B), upstream blocks stuck on "Running" after a run ends (Fix C), and a phantom "Run Error" row when the failing block already carries the error inline (Fix D). The architectural refactor of handleExecutionErrorConsole / handleExecutionCancelledConsole to accept an ExecutionConsoleDeps object is clean and consistent with the existing createBlockEventHandlers pattern. All three fixes are well-tested with 12 new unit tests, and the previously-flagged copilot cancellation path is now also wired to Fix C via executeWorkflowWithFullLogging's updated onExecutionCancelled.

Confidence Score: 5/5

Safe to merge — no P0/P1 findings; all three bug fixes are surgical and well-covered by tests.

The changes are focused, the test suite covers the happy path and the three specific failure modes, and the architectural cleanup is consistent with existing patterns. No critical logic errors or security concerns found.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts Core of all three fixes: Fix B (route duplicate error entries through updateConsoleErrorEntry), Fix C (new reconcileFinalBlockLogs reconciles still-running entries before sweep), Fix D (addExecutionErrorConsoleEntry cross-checks store for block-level errors); deps-object refactor is clean.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts Wires finalBlockLogs through all five execution-terminal paths (direct error, direct cancel, copilot error, copilot cancel, reconnect cancel); updateConsole added to both useCallback dependency arrays; no issues found.
apps/sim/app/api/workflows/[id]/execute/route.ts Adds finalBlockLogs: result.logs to three SSE emission sites (timeout error, cancellation, general execution error) so the client receives the authoritative block snapshot.
apps/sim/lib/workflows/executor/execution-events.ts Adds optional finalBlockLogs?: BlockLog[] to ExecutionErrorEvent.data and ExecutionCancelledEvent.data types; straightforward additive change.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.test.ts 12 new tests covering Fix B/C/D and the deps-object refactor; blockType filter test was corrected (per prior review thread) to share executionId so the predicate is actually exercised.
packages/testing/src/mocks/terminal-console.mock.ts New centralised mock for @/stores/terminal with an in-memory backing store for getWorkflowEntries/addConsole, enabling read-after-write test contracts without the real Zustand store.
apps/sim/stores/terminal/console/store.test.ts Adds vi.unmock calls so this test file uses the real store; adds one new test for cancelRunningEntries behaviour; existing tests preserved.
apps/sim/vitest.setup.ts Replaces the inlined terminal-console mock with the new centralised terminalConsoleMock from @sim/testing; correctly maps both @/stores/terminal and @/stores/terminal/console/store aliases.
packages/testing/src/mocks/index.ts Re-exports the three new terminal-console mock symbols; trivial barrel change.

Sequence Diagram

sequenceDiagram
    participant Server as Server (route.ts)
    participant SSE as SSE Stream
    participant Hook as useWorkflowExecution
    participant Utils as workflow-execution-utils
    participant Store as useTerminalConsoleStore

    Server->>SSE: execution:error / execution:cancelled (+ finalBlockLogs)
    SSE->>Hook: onExecutionError(data) / onExecutionCancelled(data)
    Hook->>Utils: handleExecutionErrorConsole(deps, params{finalBlockLogs})

    Note over Utils: Fix C — reconcile before sweep
    Utils->>Store: getWorkflowEntries(workflowId)
    Store-->>Utils: entries[]
    loop for each log in finalBlockLogs
        Utils->>Utils: find running entry by blockId + executionId
        Utils->>Store: updateConsole(blockId, {isRunning:false, success, error?}, executionId)
    end

    Note over Utils: sweep any still-running entries
    Utils->>Store: cancelRunningEntries(workflowId)

    Note over Utils: Fix D — skip phantom row if block already has error
    Utils->>Store: getWorkflowEntries(workflowId)
    Store-->>Utils: entries[]
    Utils->>Utils: hasBlockErrorInConsole? (executionId scoped, blockType!=error)
    alt no block-level error found
        Utils->>Store: addConsole(Run Error row)
    end

    Note over Store: Fix B (block:error SSE path)
    SSE->>Hook: block:error event
    Hook->>Utils: addConsoleErrorEntry(data)
    Utils->>Store: getWorkflowEntries — find isRunning entry for (blockId, executionId)
    alt running placeholder exists
        Utils->>Store: updateConsoleErrorEntry(data) — reuse, no duplicate
    else no running entry
        Utils->>Store: addConsole(new error entry)
    end
Loading

Reviews (3): Last reviewed commit: "fix(terminal): pass durationMs in reconn..." | Re-trigger Greptile

…tion

Address Greptile review:

- `executeWorkflowWithFullLogging`'s `onExecutionCancelled` was `() => {}`
  and silently dropped the `finalBlockLogs` payload, so Bug 2's "upstream
  blocks stuck on Running" fix did not fire on copilot-initiated
  cancellations. Wire it through `handleExecutionCancelledConsole` to
  match the SSE-route `onExecutionCancelled` path.
- Test for the `blockType !== 'error'` filter used a different
  `executionId` than the seeded entry, so the executionId scope rejected
  the entry before the blockType predicate ran. Align executionIds so
  the test actually exercises the filter.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile-apps re: "Copilot cancellation path not wired to Fix C" — fixed in dc6e3f3. executeWorkflowWithFullLogging's onExecutionCancelled now forwards durationMs and finalBlockLogs through handleExecutionCancelledConsole, matching the SSE-route path. Bug 2 reconciliation now fires on copilot-initiated cancellations.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 1, 2026

Confirmed — the fix in dc6e3f3 is correct and complete.

onExecutionCancelled now calls handleExecutionCancelledConsole with both durationMs: data?.duration and finalBlockLogs: data?.finalBlockLogs, which threads through reconcileFinalBlockLogs before sweeping remaining running entries. This is symmetric with onExecutionError's path in the same function, so copilot-initiated cancellations now get the same Bug 2 reconciliation as SSE-route cancellations.

The test fix (aligning executionId so the blockType !== 'error' predicate is actually reachable) is also a good catch — the previous test was vacuously passing because the scope filter rejected the entry before the type filter ran.

Both changes look good. The original gap is closed.

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Reconnect-resume `onExecutionCancelled` was forwarding `finalBlockLogs`
but not `data?.duration`, so the "Run Cancelled" entry rendered with a
0ms duration. Match the other two `handleExecutionCancelledConsole`
callsites.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ddcab29. Configure here.

@waleedlatif1 waleedlatif1 merged commit e2b3ae4 into staging May 1, 2026
14 checks passed
@waleedlatif1 waleedlatif1 deleted the waleedlatif1/exec-state-bugs branch May 1, 2026 02:52
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