coleam00/Archon · error

executor.backstop_triggered

executor.backstop_triggered

Error message

Workflow exited without finalizing — see logs

What it means

A safety backstop in the executor's finally block: if the workflow function exits (returns or throws) while the run is still recorded as 'running' and no terminal status write already failed, the run would remain a zombie. The backstop marks it failed with this message so terminal state always lands. It exists because of the zombie-state incident #1561; the message directs you to logs for the real cause.

Source

Thrown at packages/workflows/src/executor.ts:3237

    // Return failure result instead of re-throwing
    return { success: false, workflowRunId: workflowRun.id, error: err.message };
  } finally {
    // Release the keep-awake request FIRST — before the backstop DB calls that
    // may throw — so it always pairs with the acquire above this try, on every
    // exit path (success, thrown error, or backstop failure).
    keepAwake.release();
    // Defensive backstop: if the workflow run is still 'running' after all
    // normal and exceptional code paths, flip it to 'failed' to prevent zombie
    // accumulation. Guards against any future code path that exits without
    // calling failWorkflowRun (e.g. a generator cleanup that exits without
    // throwing). Only fires when the process stays alive long enough to run
    // this finally — see #1561 for the originating zombie-state incident.
    if (workflowRun && !terminalStatusWriteFailed) {
      const runId = workflowRun.id;
      const backstopStatus = await deps.store.getWorkflowRunStatus(runId).catch(() => null);
      if (backstopStatus === 'running') {
        getLog().warn({ workflowRunId: runId }, 'executor.backstop_triggered');
        await requireTerminalStatusWrite(
          deps.store.failWorkflowRun(runId, 'Workflow exited without finalizing — see logs'),
          { workflowRunId: runId, site: 'executor.backstop_fail_failed' }
        );
      }
    }
  }
}

View on GitHub (pinned to 0773b97458)

Solutions

  1. Check the executor logs for the workflowRunId to find the exception or early return that bypassed finalization.
  2. Fix the root cause in the workflow/node code that threw or returned without reaching finalize, then re-run the workflow.
  3. Reconcile the zombie run via CLI (`archon workflow resume` or cancel) if a status write raced or was lost.
  4. Add tests covering throw and early-return exit paths so finalize is always reached.

Example fix

// before: early return that skips finalize
if (!nodes.length) return;
// after: route through the executor's error/finalize path
if (!nodes.length) {
  throw new Error('Workflow has no nodes'); // executor terminalizes the run
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await executeWorkflow(/* ... */);
} catch (err) {
  // guarantee terminalization even on unexpected throws
  await store.failWorkflowRun(runId, `Unhandled executor error: ${err}`);
  throw err;
}

Prevention

When it happens

Trigger: The executor's finally block runs with workflowRun set, terminalStatusWriteFailed false, and store.getWorkflowRunStatus(runId) === 'running' — i.e. the workflow body returned early or threw an error that bypassed the normal finalize path (executor.ts:3237), and the process stayed alive long enough for the finally to execute.

Common situations: An uncaught exception in a node handler outside normal error handling; a code change adding an early return before finalization; an abort/OOM mid-node skipping completion; custom node types that swallow control flow.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/8f07eb47303ca80a. Report an issue: GitHub.