Skip to content

workflow: the drawer commits a duplicate link when a pair is drawn twice, and the host rejects the whole save #2163

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 1d0a0e4d0dcc4a23b78f8b1c2de956e9
Branch: dev/4-workflow-drawer-duplicate-edge
Base: 0bfaf52
At: 2026-11-02T12:51:59Z

What goes wrong

In the workflow drawer (the Edit plane on a workflow page), drawing a link between two Bots that already have one commits the same from → to pair twice:

  • dragging from the source's output port onto a Bot it is already wired to appends a second identical edge (plugins/workflow/client.js:1152, and the second canvas at :1811);
  • reconnecting an existing link's endpoint onto a third Bot that already shares the same source builds rest.concat([{ from, to }]) where rest only removes the dragged link (plugins/workflow/client.js:1155), so the pair that already existed survives and is committed twice.

The host refuses the whole save: plugins/workflow/src/graph.js:249-256 throws Link A → B is listed twice, which surfaces through callError as a wf-err line while the canvas keeps drawing from workflow.edges — so the duplicate link is never visible, and the user sees only an unexplained error.

For the reconnect case this is not cosmetic: when coder → tester exists and coder → reviewer is dragged onto tester, the commit is always rejected, so "merge coder's two links into one" has no working path in the drawer.

The Settings canvas already behaves the other way for the same gesture: core/src/client/workflows.js:72-76 connectNodes returns the list unchanged when the pair exists.

Reproduction (at base 0bfaf525e8545e4f7910797218c548ad54f15559)

  1. Start with a workflow whose edges include writer → tester; in the drawer drag a new wire from Writer's output port onto Tester. The commit contains writer → tester twice and the host answers Link writer → tester is listed twice.
  2. Start with coder → tester and coder → reviewer; reconnect the coder → reviewer endpoint onto tester. The commit contains coder → tester twice and the host answers Link coder → tester is listed twice.
  3. Control: dragging onto a Bot with no existing pair still adds exactly one edge.

Expected

  • Drawing a pair that already exists changes nothing: the commit carries exactly one from → to, and no error is shown.
  • Reconnecting an endpoint onto a Bot that already shares the source yields exactly one from → to for that source; the dragged link is gone.
  • The host keeps rejecting duplicate links sent through the API (normalizeGraph stays fail-closed); only the drawer stops producing them.

Scope

  • plugins/workflow/client.js — one local pair guard used by the three commit sites (the file is a standalone script with no imports, so the guard lives in the file).
  • plugins/workflow/test/client.test.js — one test per case above, red at the base and green after, plus the existing "wire to a new Bot" test left green.
  • No patches/ds-bot/** regeneration: the plugins/workflow client is not mirrored there (patches/ds-bot/files/ covers core/ only), verified by a code search for plugins/workflow/client.js under patches/ returning 0 hits.

Duplicate check

Searched the repository for the same defect before opening: "listed twice" (8 hits, all closed and about unrelated wording), duplicate edge / duplicate wire in titles (0), reconnect duplicate (5, all unrelated). No open issue and no open PR covers a duplicate link committed by the drawer. P-401 (withdrawing an existing link) is a different capability and its fix does not overlap this guard.

Activity

  1. HugoXK commented on Nov 2, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 1d0a0e4d0dcc4a23b78f8b1c2de956e9
    Reason: merged as 09f8fe7 at 2026-11-02T13:20:28Z
    At: 2026-11-02T13:22:17Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions