coleam00/Archon · error

Workflow run not found or already terminal (id: ${id})

Error message

Workflow run not found or already terminal (id: ${id})

What it means

failWorkflowRun() updates only rows whose status is IN ('running','pending'); terminal rows (completed/failed/cancelled) never transition. If rowCount is 0 — id unknown or run already terminal — this error is thrown so callers never silently re-fail or re-stamp a finished run.

Source

Thrown at packages/core/src/db/workflows.ts:1302

        });
      }
      if ((update.rowCount ?? 0) > 0) {
        await insertWorkflowEvent(query, {
          workflow_run_id: id,
          event_type: 'workflow_failed',
          data: { error },
        });
      }
      return update;
    });
  } catch (dbError) {
    const err = dbError as Error;
    getLog().error({ err }, 'db.workflow_run_mark_failed_error');
    throw new Error(`Failed to fail workflow run: ${err.message}`);
  }
  if (result.rowCount === 0) {
    getLog().warn({ workflowRunId: id }, 'db.workflow_run_fail_no_match');
    throw new Error(`Workflow run not found or already terminal (id: ${id})`);
  }
}

export async function cancelWorkflowRun(
  id: string,
  event?: WorkflowCancellationEventDetails
): Promise<{ cancelled: boolean }> {
  const dialect = getDialect();
  let result: Awaited<ReturnType<IDatabase['query']>>;
  try {
    // Guard against re-stamping an already-finished run. Cancelling a run that
    // is 'completed' or 'cancelled' must be a no-op, not a re-write of
    // completed_at / a resurrection of terminal state. 'failed' is intentionally
    // still cancellable (it remains a resumable state, so the user must be able
    // to discard it), and a 'running' run stays cancellable — that is
    // cooperative cancellation, which the executor honors via its between-layer
    // status check (dag-executor).
    result = await getDatabase().withTransaction(async query => {

View on GitHub (pinned to 0773b97458)

Solutions

  1. Check the run's current status first; treat 'already terminal' as expected and skip re-failing
  2. Serialize failure handling so only one code path can fail a run (guard with a local flag or re-read after the error)
  3. If the run id may be stale, re-resolve the id from the run record before failing
  4. If you need to record additional error info on a terminal run, write an event/log instead of mutating status
  5. Retry only if you confirmed the run is in 'running' or 'pending' state

Example fix

// before
try { await failWorkflowRun(id, msg); } catch { /* retried blindly */ }
// after
try {
  await failWorkflowRun(id, msg);
} catch (err) {
  if (err.message.includes('already terminal') || err.message.includes('not found')) {
    getLog().info({ runId: id }, 'run already terminal; skipping fail');
  } else { throw err; }
}
Defensive patterns

Strategy: validation

Validate before calling

const run = await getWorkflowRun(id);
if (!run) throw new Error(`run ${id} does not exist`);
if (['completed', 'failed', 'cancelled'].includes(run.status)) {
  return; // already terminal: nothing to do
}

Type guard

function isNonTerminal(run: { status: string } | null | undefined): boolean {
  return run?.status === 'running' || run?.status === 'pending';
}

Try / catch

try {
  await failWorkflowRun(id, msg);
} catch (err) {
  if (err.message.includes('not found or already terminal')) {
    getLog().info({ runId: id }, 'run already terminal; failure not re-recorded');
  } else { throw err; }
}

Prevention

When it happens

Trigger: Calling failWorkflowRun(id, ...) for: a nonexistent id; a run already 'failed' (double-failure, e.g. step failure then outer catch fails again); a run that was cancelled or completed concurrently; or a run id from a different/older database.

Common situations: Executor error handler and a finally/cleanup path both call failWorkflowRun; operator cancelled the run just before it errored; replaying failure handling after a crash against an already-terminal row; stale id cached from a previous run.

Related errors


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