Skip to content

fix(executor): stop HITL error edges from firing on successful resume#5152

Merged
icecrasher321 merged 2 commits into
stagingfrom
fix/hitl-exec-err
Jun 20, 2026
Merged

fix(executor): stop HITL error edges from firing on successful resume#5152
icecrasher321 merged 2 commits into
stagingfrom
fix/hitl-exec-err

Conversation

@icecrasher321

@icecrasher321 icecrasher321 commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

A human_in_the_loop pause block wired to an error handle (e.g. an error-notifier) fired on every successful run. On resume, the engine released all edges out of completed pause blocks and force-marked every target as activated, ignoring sourceHandle — so the pause block's error edge was treated as fired, and the convergence check later ran the error branch even though nothing errored.

Type of Change

  • Bug fix

Testing

Added executor tests

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)

@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 20, 2026 7:33pm

Request Review

@cursor

cursor Bot commented Jun 20, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes core workflow resume routing in the executor; behavior is well covered by new tests but affects edge activation and join scheduling on every HITL resume.

Overview
Fixes human-in-the-loop resume so pause blocks no longer treat error outgoing edges as fired on a successful continuation.

On resume, ExecutionEngine now resolves each remainingEdges entry’s sourceHandle (from the snapshot or the live DAG via resolveRemainingEdgeHandle). error edges are deactivated through EdgeManager.deactivateResumedEdge instead of removing incoming edges and force-marking targets activated. Continuation edges still follow the prior “release edge / activate when ready” path. When a join was already activated and becomes ready after pruning a pause error edge, it is re-queued.

EdgeManager adds hasActivatedEdge and deactivateResumedEdge (wrapper around cascade deactivation). Executor tests cover error-notifier false positives, real upstream errors, convergence joins, and source-vs-error to the same target.

Reviewed by Cursor Bugbot for commit 738b42f. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where a human_in_the_loop pause block wired to an error handler would incorrectly fire that error handler on every successful resume. The old initializeQueue path blindly activated every target of every remaining edge, ignoring sourceHandle, causing error edges to be treated as fired even when nothing errored.

  • The fix adds resolveRemainingEdgeHandle in the engine, which looks up the live DAG when the persisted edge lacks a sourceHandle and prefers a non-error handle when both exist for the same source→target pair. Error edges are deactivated via the new deactivateResumedEdge path rather than activated, and convergence join nodes already genuinely activated are still re-queued.
  • EdgeManager gains two new public methods: hasActivatedEdge (Set lookup) and deactivateResumedEdge (thin wrapper over the existing deactivateEdgeAndDescendants). Three JSDoc comments were removed without replacement, including the one that explained the non-obvious loop-correctness invariant on clearDeactivatedEdgesForNodes.

Confidence Score: 4/5

Safe to merge — the fix is well-scoped, the new code paths are fully exercised by six new unit/integration tests, and the underlying edge-readiness invariants hold through manual traces.

The fix is correct and the test suite is thorough, covering the primary bug, the real-error case, the convergence join scenario, and the dual source+error edge case. The only notable loss is the removal of JSDoc comments that documented non-obvious loop-correctness invariants on clearDeactivatedEdgesForNodes and the implicit cascadeTargets = undefined behavior of deactivateResumedEdge.

No files require special attention beyond the removed JSDoc in edge-manager.ts.

Important Files Changed

Filename Overview
apps/sim/executor/execution/engine.ts Core fix: initializeQueue now calls resolveRemainingEdgeHandle to distinguish error from continuation edges on resume; error edges are deactivated instead of activated. A new resolveRemainingEdgeHandle helper falls back to the live DAG when persisted edges lack sourceHandle, preferring non-error handles when both exist on the same source→target pair.
apps/sim/executor/execution/edge-manager.ts Adds two public methods: hasActivatedEdge (simple Set lookup) and deactivateResumedEdge (thin wrapper around existing deactivateEdgeAndDescendants). Three JSDoc comments were removed without replacement.
apps/sim/executor/execution/engine.test.ts Adds three targeted integration tests: error edge pruning on successful resume, re-queuing of a convergence join when a pause error edge is pruned, and continuation handle preference when source and error edges share the same target.
apps/sim/executor/execution/edge-manager.test.ts Adds three unit tests for the new deactivateResumedEdge method covering: pure deactivation without firing the error target, error target still fires when a real upstream block errors, and convergence join remains activated+ready after pruning a pause error edge.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Engine as ExecutionEngine
    participant EM as EdgeManager
    participant DAG as DAG

    Note over Engine: initializeQueue() — resume from snapshot
    loop for each edge in remainingEdges
        Engine->>DAG: nodes.get(edge.source)
        Engine->>Engine: resolveRemainingEdgeHandle(edge)
        alt "sourceHandle === EDGE.ERROR"
            Engine->>EM: deactivateResumedEdge(source, target, EDGE.ERROR)
            EM->>EM: deactivateEdgeAndDescendants(source, target, handle)
            Engine->>EM: hasActivatedEdge(targetNode.id)
            Engine->>EM: isNodeReady(targetNode)
            alt already activated AND ready (convergence join)
                Engine->>Engine: addToQueue(targetNode.id)
            end
        else sourceHandle is continuation / undefined
            Engine->>EM: markNodeWithActivatedEdge(targetNode.id)
            Engine->>EM: isNodeReady(targetNode)
            opt ready
                Engine->>Engine: addToQueue(targetNode.id)
            end
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Engine as ExecutionEngine
    participant EM as EdgeManager
    participant DAG as DAG

    Note over Engine: initializeQueue() — resume from snapshot
    loop for each edge in remainingEdges
        Engine->>DAG: nodes.get(edge.source)
        Engine->>Engine: resolveRemainingEdgeHandle(edge)
        alt "sourceHandle === EDGE.ERROR"
            Engine->>EM: deactivateResumedEdge(source, target, EDGE.ERROR)
            EM->>EM: deactivateEdgeAndDescendants(source, target, handle)
            Engine->>EM: hasActivatedEdge(targetNode.id)
            Engine->>EM: isNodeReady(targetNode)
            alt already activated AND ready (convergence join)
                Engine->>Engine: addToQueue(targetNode.id)
            end
        else sourceHandle is continuation / undefined
            Engine->>EM: markNodeWithActivatedEdge(targetNode.id)
            Engine->>EM: isNodeReady(targetNode)
            opt ready
                Engine->>Engine: addToQueue(targetNode.id)
            end
        end
    end
Loading

Reviews (1): Last reviewed commit: "fix(executor): stop HITL error edges fro..." | Re-trigger Greptile

Comment thread apps/sim/executor/execution/edge-manager.ts
@icecrasher321 icecrasher321 merged commit 35a7bf6 into staging Jun 20, 2026
15 checks passed
@icecrasher321 icecrasher321 deleted the fix/hitl-exec-err branch June 20, 2026 19:41
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