Skip to content

fix(workflow): the history row a graph edit writes carries no id, so the stopped attempt is never marked superseded #1611

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: 0a5324772fb14d038262688a288a525a
Branch: dev3/fix/history-row-id
Base: 655aa71
At: 2026-10-29T00:20:22Z

Problem

#1604 taught the Runs list to mark an older attempt as superseded by <id>, but two of the six places that append to lane.history still push a bare row with no id. On those two paths the marker can never appear, because the client skips rows without an id.

Live main 655aa714609b34836d4b021072f6a3cbe593f0dd, plugins/workflow/src/service.js:

  • :667-675 historyRow(run) returns { id, task, status, at, failure? }; used at :606, :662, :1099, :1240.
  • :1391 (saveGraph — the edit dropped a node that was still running, so the run is ended) still pushes { task: run.task, status: run.status, at: run.updatedAt } — no id, although run.id is in scope.
  • :1629 (purgeDemos migration) pushes { task: run.task, status: run.status, at: run.updatedAt ?? 0 } — no id (note: there the demo map key is id, so run.id may be absent and the key must be used).

plugins/workflow/client.js:746-770 markSuperseded(rows) refuses to mark such a row: if (!row.id || !newest.id) continue. client.js:284 (history: [...(laneOf(item.id).history ?? [])]) hands the rows to the client unchanged.

Reproduction

  1. Start a workflow run and let it reach a running node.
  2. In the graph editor, delete that running node and save. saveGraph ends the run, pushes the bare history row (:1391) and emits the ■ 已停止 system message.
  3. Start the same task again, let the new attempt finish, open Runs/History.

Observed: the old row has no superseded by marker — it reads like a sibling of the retry. Expected: the older attempt is marked as superseded by the newer one, which is the whole point of #1507/#1604.

A probe with the host's real row shape (history: [{ task, status: 'failed', at }] plus one live run with an id) imported from client.js reports supersededBy=undefined / expected the older host row to name the newer attempt, while plugins/workflow/test/client.test.js stays green — the existing #1507 test (around :2112) builds its input by hand with id: 'run-old', a field saveGraph never writes.

Scope (S)

  1. service.js:1391 → use the existing historyRow(run).
  2. service.js:1629 → historyRow(run), with no id fallback. The id in that loop is the lane key (const lane = state.lanes[id] at :1626), not a run id: using it would write a workflow id into a history row, and markSuperseded would render retry of <workflow id>; runHistoryRows would also treat it as a run id when deduping. A legacy demo row without run.id therefore stays without an id and markSuperseded skips it, exactly as today.
  3. Add one regression test that builds the input from the host's real shape (a row from the saveGraph end path — no hand-written id) and asserts the older attempt gets supersededBy / retryOf; keep the existing #1507 test green.

Out of scope: changing nodeStatus, rendering copy, or a general "client fixtures must come from the host" contract (worth a separate later item; not needed for this fix). Do not refactor historyRow or the four appends that already use it — only the two bare pushes at :1391 and :1629 change, so this cannot collide with any follow-up to #1604.

Acceptance

  • Deleting a running node, then re-running the task, marks the stopped attempt as superseded in the Runs list.
  • The new test fails on 655aa714 and passes with the fix; the existing #1507 test is unchanged and green.
  • The regression test pins all three shapes, not just "it works now":
    1. the row the saveGraph end path pushes (:1391) carries the run's real id;
    2. the row purgeDemos pushes (:1629) carries the run's id and the test asserts it is not the lane key — a demo record under lane wf-x with run.id: 'run-demo' must yield id: 'run-demo', so a future id fallback fails the suite; a demo record without run.id yields a row with no id;
    3. a legacy row with no id is still skipped by markSuperseded while the newer attempt is marked — "same as today" is an assertion, not prose.
  • No history row ever carries a lane id as its id (no fake retry of <workflow id>).
  • plugins/workflow suite green, npm run verify exit 0 at the PR tip.

Branch

dev3/fix/history-row-id

Activity

  1. HugoXK commented on Oct 29, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 0a5324772fb14d038262688a288a525a
    Reason: Issue closed via merged #1613; claim no longer needed.
    At: 2026-10-29T02:18:28Z

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions