different-ai/openwork · error

Workflow response was incomplete.

Error message

Workflow response was incomplete.

What it means

parseWorkflowDetail validates a workflow-library payload: it must be a record with a record at 'workflow' and an array at 'views'; otherwise it throws 'Workflow response was incomplete.' It then narrows enum fields (role, state, resultState, viewState, source.kind) to literal unions for the WorkflowLibraryDetail type.

Source

Thrown at ee/apps/den-web/app/(den)/dashboard/_components/workflow-detail-data.tsx:26

  type WorkflowDetail,
} from "@openwork/types/workflows";
import { getErrorMessage, requestJson } from "../../_lib/den-flow";

type WorkflowSummary = {
  type: "workflow"; id: string; plugin: { id: string; name: string } | null; name: string; description: string | null;
  role: "viewer" | "editor" | "manager"; state: "ready" | "needs_signin" | "needs_admin_setup";
  resultState: "never_run" | "fresh" | "stale" | "needs_attention"; latestSuccessfulAt: string | null;
  viewState: "default" | "custom_active" | "build_failed" | "retired"; activeViewTitle: string | null;
  automationCount: number; source: { kind: "created" | "installed_template" };
};
export type WorkflowLibraryDetail = { workflow: WorkflowSummary; script: WorkflowDetail; views: GeneratedArtifactView[] };

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

function parseWorkflowDetail(value: unknown): WorkflowLibraryDetail {
  if (!isRecord(value) || !isRecord(value.workflow) || !Array.isArray(value.views)) throw new Error("Workflow response was incomplete.");
  const workflow = value.workflow;
  const role = workflow.role === "viewer" || workflow.role === "editor" || workflow.role === "manager" ? workflow.role : null;
  const state = workflow.state === "ready" || workflow.state === "needs_signin" || workflow.state === "needs_admin_setup" ? workflow.state : null;
  const resultState = workflow.resultState === "never_run" || workflow.resultState === "fresh" || workflow.resultState === "stale" || workflow.resultState === "needs_attention" ? workflow.resultState : null;
  const viewState = workflow.viewState === "default" || workflow.viewState === "custom_active" || workflow.viewState === "build_failed" || workflow.viewState === "retired" ? workflow.viewState : null;
  const sourceKind = isRecord(workflow.source) && (workflow.source.kind === "created" || workflow.source.kind === "installed_template") ? workflow.source.kind : null;
  const plugin = isRecord(workflow.plugin) && typeof workflow.plugin.id === "string" && typeof workflow.plugin.name === "string" ? { id: workflow.plugin.id, name: workflow.plugin.name } : null;
  if (workflow.type !== "workflow" || typeof workflow.id !== "string" || (workflow.plugin !== null && !plugin) || typeof workflow.name !== "string" || !role || !state || !resultState || !viewState || !sourceKind || typeof workflow.automationCount !== "number") {
    throw new Error("Workflow response was incomplete.");
  }
  return {
    workflow: {
      type: "workflow", id: workflow.id, plugin, name: workflow.name,
      description: typeof workflow.description === "string" ? workflow.description : null,
      role, state, resultState,
      latestSuccessfulAt: typeof workflow.latestSuccessfulAt === "string" ? workflow.latestSuccessfulAt : null,
      viewState, activeViewTitle: typeof workflow.activeViewTitle === "string" ? workflow.activeViewTitle : null,
      automationCount: workflow.automationCount, source: { kind: sourceKind },

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Log the incoming payload and check for workflow/views keys to see which contract broke.
  2. Update parseWorkflowDetail to match the current API shape, or fix/redeploy the API to return {workflow: {...}, views: [...]}.
  3. Ensure the query hits the library-detail endpoint, not another workflow route.
  4. Add a tolerant fallback (e.g. default views: []) if the API legitimately omits views for restricted roles.

Example fix

// before
if (!isRecord(value) || !isRecord(value.workflow) || !Array.isArray(value.views)) throw new Error("Workflow response was incomplete.");
// after
const body = isRecord(value) && isRecord(value.data) ? value.data : value;
if (!isRecord(body) || !isRecord(body.workflow) || !Array.isArray(body.views ?? [])) throw new Error("Workflow response was incomplete.");
const views = Array.isArray(body.views) ? body.views : [];
Defensive patterns

Strategy: type-guard

Validate before calling

function looksLikeLibraryDetail(v: unknown): boolean {
  return isRecord(v) && isRecord(v.workflow) && Array.isArray(v.views);
}

Type guard

function isWorkflowLibraryPayload(v: unknown): v is { workflow: Record<string, unknown>; views: unknown[] } {
  return isRecord(v) && isRecord(v.workflow) && Array.isArray(v.views);
}

Try / catch

try {
  const detail = parseWorkflowDetail(payload);
} catch (e) {
  if (e instanceof Error && e.message === "Workflow response was incomplete.") {
    console.error("library detail payload:", payload);
    // fall back to list-level data or empty state
  } else throw e;
}

Prevention

When it happens

Trigger: useWorkflowLibraryDetail receives a payload that is not a record, lacks value.workflow as a record, or lacks value.views as an array — e.g. wrong endpoint response, error body delivered with 200, or API contract change.

Common situations: Client pointed at the wrong route (detail vs list endpoint); server deploy changed the wrapper shape (e.g. workflow moved under data); permission-limited response omitting views; proxy returning HTML/empty body parsed as a non-record.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/36d7ce7f8df894c6. Report an issue: GitHub.