{"record":{"id":"36d7ce7f8df894c6","repo":"different-ai/openwork","slug":"workflow-response-was-incomplete","errorCode":null,"errorMessage":"Workflow response was incomplete.","messagePattern":"Workflow response was incomplete\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/workflow-detail-data.tsx","lineNumber":26,"sourceCode":"  type WorkflowDetail,\n} from \"@openwork/types/workflows\";\nimport { getErrorMessage, requestJson } from \"../../_lib/den-flow\";\n\ntype WorkflowSummary = {\n  type: \"workflow\"; id: string; plugin: { id: string; name: string } | null; name: string; description: string | null;\n  role: \"viewer\" | \"editor\" | \"manager\"; state: \"ready\" | \"needs_signin\" | \"needs_admin_setup\";\n  resultState: \"never_run\" | \"fresh\" | \"stale\" | \"needs_attention\"; latestSuccessfulAt: string | null;\n  viewState: \"default\" | \"custom_active\" | \"build_failed\" | \"retired\"; activeViewTitle: string | null;\n  automationCount: number; source: { kind: \"created\" | \"installed_template\" };\n};\nexport type WorkflowLibraryDetail = { workflow: WorkflowSummary; script: WorkflowDetail; views: GeneratedArtifactView[] };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null;\n}\n\nfunction parseWorkflowDetail(value: unknown): WorkflowLibraryDetail {\n  if (!isRecord(value) || !isRecord(value.workflow) || !Array.isArray(value.views)) throw new Error(\"Workflow response was incomplete.\");\n  const workflow = value.workflow;\n  const role = workflow.role === \"viewer\" || workflow.role === \"editor\" || workflow.role === \"manager\" ? workflow.role : null;\n  const state = workflow.state === \"ready\" || workflow.state === \"needs_signin\" || workflow.state === \"needs_admin_setup\" ? workflow.state : null;\n  const resultState = workflow.resultState === \"never_run\" || workflow.resultState === \"fresh\" || workflow.resultState === \"stale\" || workflow.resultState === \"needs_attention\" ? workflow.resultState : null;\n  const viewState = workflow.viewState === \"default\" || workflow.viewState === \"custom_active\" || workflow.viewState === \"build_failed\" || workflow.viewState === \"retired\" ? workflow.viewState : null;\n  const sourceKind = isRecord(workflow.source) && (workflow.source.kind === \"created\" || workflow.source.kind === \"installed_template\") ? workflow.source.kind : null;\n  const plugin = isRecord(workflow.plugin) && typeof workflow.plugin.id === \"string\" && typeof workflow.plugin.name === \"string\" ? { id: workflow.plugin.id, name: workflow.plugin.name } : null;\n  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\") {\n    throw new Error(\"Workflow response was incomplete.\");\n  }\n  return {\n    workflow: {\n      type: \"workflow\", id: workflow.id, plugin, name: workflow.name,\n      description: typeof workflow.description === \"string\" ? workflow.description : null,\n      role, state, resultState,\n      latestSuccessfulAt: typeof workflow.latestSuccessfulAt === \"string\" ? workflow.latestSuccessfulAt : null,\n      viewState, activeViewTitle: typeof workflow.activeViewTitle === \"string\" ? workflow.activeViewTitle : null,\n      automationCount: workflow.automationCount, source: { kind: sourceKind },","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/workflow-detail-data.tsx#L8-L44","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log the incoming payload and check for workflow/views keys to see which contract broke.","Update parseWorkflowDetail to match the current API shape, or fix/redeploy the API to return {workflow: {...}, views: [...]}.","Ensure the query hits the library-detail endpoint, not another workflow route.","Add a tolerant fallback (e.g. default views: []) if the API legitimately omits views for restricted roles."],"exampleFix":"// before\nif (!isRecord(value) || !isRecord(value.workflow) || !Array.isArray(value.views)) throw new Error(\"Workflow response was incomplete.\");\n// after\nconst body = isRecord(value) && isRecord(value.data) ? value.data : value;\nif (!isRecord(body) || !isRecord(body.workflow) || !Array.isArray(body.views ?? [])) throw new Error(\"Workflow response was incomplete.\");\nconst views = Array.isArray(body.views) ? body.views : [];","handlingStrategy":"type-guard","validationCode":"function looksLikeLibraryDetail(v: unknown): boolean {\n  return isRecord(v) && isRecord(v.workflow) && Array.isArray(v.views);\n}","typeGuard":"function isWorkflowLibraryPayload(v: unknown): v is { workflow: Record<string, unknown>; views: unknown[] } {\n  return isRecord(v) && isRecord(v.workflow) && Array.isArray(v.views);\n}","tryCatchPattern":"try {\n  const detail = parseWorkflowDetail(payload);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Workflow response was incomplete.\") {\n    console.error(\"library detail payload:\", payload);\n    // fall back to list-level data or empty state\n  } else throw e;\n}","preventionTips":["Verify the query targets the library-detail endpoint, not list/detail-by-id routes","Unwrap common envelopes ({data}) in the fetch layer before parsing","Keep enum narrowing lists in sync with server-side enum definitions"],"tags":["api","schema","response-parsing"],"backgroundTag":"schema-validation-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}