Resolving an incident with Retry, Resume or Skip tells the operator the run has moved on, and on a site running the shipped default execution mode the run does not move until the next cron.

Problem/Motivation

Every other way the engine puts a token back in play advances a synchronous run inline once its work commits: signal() and spawn() drain from the executor themselves, and a timeout fired on demand asks for it by name in TimeoutSweeper::fireNow(), with a comment saying why. The incident actions are the third case, and they do not. retryIncident() and resumeIncident() re-queue the errored token, skipIncident() produces the failed node successors, and each then commits and returns, leaving the instance with an active token, a queue item, and nothing in the request to drive either.

execution_mode ships as synchronous, so this is the default behaviour rather than an opt-in one: the operator presses Retry on the instance page, reads "Incident 3 resolved (retry)", and the run sits exactly where it was. The same holds for the Resume form and for any caller of the incident actions on the engine.

Observed against 1.x in synchronous mode, with no queue drain after the action: retry leaves the instance running and its token active on the failed node; resume the same; skip leaves the instance running with its token active on the next node. Cancel and fail are correct as they stand, because neither leaves an active token for anything to advance.

Steps to reproduce

  1. Leave the execution mode at its default, synchronous.
  2. Run a workflow whose node throws until its attempts are spent, so the branch dead-letters into an open incident.
  3. Fix the cause and press Retry on the incident, from the instance page.
  4. The message says the incident is resolved. The instance is still running and its token is still active, until the next cron run.

Proposed resolution

Drain in the engine rather than in any one surface, so the instance page, the Resume form, the API and a third-party operator tool all get it, the way WorkflowEngine::retryToken() already does. Each of the three resolutions that leaves work to do asks WorkflowExecutor::drainSynchronous() once its own transaction has committed. The executor keeps the decision, so a queued run and a nested call inside another drain are both no-ops and nothing changes for a site that asked for queued execution.

Why no test caught it

EngineIncidentTest::setUp() pins execution_mode to queued, deliberately, so that it can drive advancement by hand. The default lane was therefore never exercised for any incident action. The merge request adds a class that pins nothing and makes every assertion with no queue drain after the operator acts.

User interface changes

None. The existing message stops being misleading, but its wording is unchanged.

API changes

None. No signature changes, no new setting.

Data model changes

None.

Release notes snippet

Retrying, resuming or skipping an incident now advances a synchronous run in the same request, instead of leaving it for the next cron run.

AI-Generated: Yes (Claude Code was used to help draft this issue summary and to write the code and tests on the merge request. I reviewed and ran the work myself before posting it.)

Issue fork orchestra-3622871

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review

  • mably committed c81fb3b9 on 1.x
    fix: #3622871 Advance a synchronous run when an incident is retried,...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.