coleam00/Archon · error
Failed to fail workflow run
Error message
Failed to fail workflow run: ${err.message} What it means
failWorkflowRun() marks a run 'failed' (from 'running' or 'pending') in a transaction and records a workflow_failed event. This wrapper rethrows any database error from that transaction with the original message appended. It is distinct from the 'no matching row' case (error 263).
Solutions
- Read the appended underlying message and the 'db.workflow_run_mark_failed_error' log entry
- If scheduledResume was passed, validate it against scheduledWorkflowResumeSchema before calling (resumeAt, deadlineAt, attempt, max_attempts)
- Verify database connectivity and retry once the DB is healthy (the run must still be non-terminal)
- Confirm schema/migrations are current for remote_agent_workflow_runs and the workflow events table
- If failing due to an invalid resume schedule, retry without the scheduledResume argument
Example fix
// before await failWorkflowRun(id, errMsg, resume as ScheduledWorkflowResume); // after const parsed = scheduledWorkflowResumeSchema.safeParse(resume); await failWorkflowRun(id, errMsg, parsed.success ? parsed.data : undefined);
Defensive patterns
Strategy: try-catch
Validate before calling
const run = await getWorkflowRun(id);
if (!run) throw new Error(`run ${id} does not exist`);
if (!['running', 'pending'].includes(run.status)) throw new Error(`run ${id} already ${run.status}`);
if (scheduledResume) scheduledWorkflowResumeSchema.parse(scheduledResume); // throws early with clear message Type guard
function isScheduledResume(v: unknown): v is ScheduledWorkflowResume {
return scheduledWorkflowResumeSchema.safeParse(v).success;
} Try / catch
try {
await failWorkflowRun(id, message, scheduledResume);
} catch (err) {
getLog().error({ err, runId: id }, 'failWorkflowRun db error');
if (isTransientDbError(err)) await backoffThenRetry(() => failWorkflowRun(id, message));
else throw err;
} Prevention
- Validate scheduledResume against the schema before calling
- Ensure DB connectivity during executor shutdown/cleanup paths
- Keep SQLite/PostgreSQL schemas aligned
- Check run status is 'running' or 'pending' before failing
- Route all failure writes through one owner to reduce transaction contention
When it happens
Trigger: Calling failWorkflowRun(id, error, scheduledResume?) when the UPDATE or the workflow_failed/quota_resume_scheduled event insert throws: DB connection failure, constraint violation on the event insert, invalid scheduledResume failing scheduledWorkflowResumeSchema.parse (thrown inside the try), or dialect-specific JSON manipulation error.
Common situations: Database outage while an executor is trying to record a failure; scheduledResume object missing required fields (resumeAt/deadlineAt/attempt/max_attempts) so schema.parse throws inside the try block; event table schema drift between SQLite and PostgreSQL; transaction rollback from a deadlock.
Related errors
- Failed to complete workflow run
- Workflow run not found or already terminal
- Workflow run not found or not in running state
- Detached work stopped, but cancellation did not win the run…
- Failed to abandon workflow run
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/bab2b2b4bd56fbb3.
Report an issue: GitHub.
Appendix: source
Thrown at packages/core/src/db/workflows.ts:1298
deadline_at: parsedSchedule.deadlineAt,
attempt: parsedSchedule.attempt,
max_attempts: parsedSchedule.maxAttempts,
},
});
}
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 ableView on GitHub (pinned to 0773b97458)