coleam00/Archon · error

Workflow run not found or not in running state (id: ${id})

Error message

Workflow run not found or not in running state (id: ${id})

What it means

completeWorkflowRun() guards its UPDATE with 'WHERE id = $1 AND status = 'running''. If rowCount is 0 the run either does not exist or is not currently running, so the library refuses to stamp completion — protecting terminal state from being overwritten. Unlike cancel, this is a thrown error, not an idempotent no-op.

Source

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

            [id]
          );
      if ((update.rowCount ?? 0) > 0) {
        await insertWorkflowEvent(query, {
          workflow_run_id: id,
          event_type: 'workflow_completed',
          data: completion,
        });
      }
      return update;
    });
  } catch (error) {
    const err = error as Error;
    getLog().error({ err }, 'db.workflow_run_complete_failed');
    throw new Error(`Failed to complete workflow run: ${err.message}`);
  }
  if (result.rowCount === 0) {
    getLog().warn({ workflowRunId: id }, 'db.workflow_run_complete_no_match');
    throw new Error(`Workflow run not found or not in running state (id: ${id})`);
  }
}

/**
 * Mark a run failed.
 *
 * Matches `pending` as well as `running`. A run can fail BEFORE it ever transitions to
 * running — source capture, artifact setup, and credential resolution all happen against
 * a freshly inserted `pending` row — and a `running`-only guard left those rows pending
 * forever: no terminal state, no error recorded, and nothing to tell the operator the run
 * is dead. Both are non-terminal states owned by this process, so failing either is the
 * same decision. Terminal rows still never transition.
 */
export async function failWorkflowRun(
  id: string,
  error: string,
  scheduledResume?: ScheduledWorkflowResume
): Promise<void> {

View on GitHub (pinned to 0773b97458)

Solutions

  1. Fetch the run by id and check its status before completing
  2. Only call completeWorkflowRun for runs confirmed in 'running' state
  3. If the run is already terminal, treat completion as done (idempotent) instead of retrying
  4. If the run is 'pending', first transition it to running per the executor lifecycle, or use failWorkflowRun (which also matches pending)
  5. Verify the id is the correct workflow run id (not a step/event id)

Example fix

// before
await completeWorkflowRun(runId, { duration_ms });
// after
const run = await getWorkflowRun(runId);
if (run?.status === 'running') {
  await completeWorkflowRun(runId, { duration_ms });
} else {
  getLog().warn({ runId, status: run?.status }, 'skip complete: not running');
}
Defensive patterns

Strategy: validation

Validate before calling

const run = await getWorkflowRun(id);
if (!run || run.status !== 'running') {
  throw new Error(`cannot complete run ${id}: status=${run?.status ?? 'missing'}`);
}

Type guard

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

Try / catch

try {
  await completeWorkflowRun(id, { duration_ms });
} catch (err) {
  if (err.message.includes('not found or not in running state')) {
    const run = await getWorkflowRun(id);
    getLog().info({ id, status: run?.status }, 'completion skipped: run not running');
  } else { throw err; }
}

Prevention

When it happens

Trigger: Calling completeWorkflowRun(id, ...) for: an id that was never inserted; a run already 'completed', 'failed', or 'cancelled'; a run still 'pending' (never transitioned to running); or a concurrent terminal transition that won a race.

Common situations: Caller completed the run twice (double completion in executor and a finally block); run failed earlier so failWorkflowRun already set terminal state; operator cancelled the run while it was finishing; typo'd or foreign run id; resumed run rows replaced with new ids.

Related errors


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