{"record":{"id":"7bad0c808a1851cd","repo":"different-ai/openwork","slug":"invalid-plugin-payload","errorCode":"invalid_plugin_payload","errorMessage":"Plugin was created but no id was returned.","messagePattern":"Plugin was created but no id was returned\\.","errorType":"error_code","errorClass":"DenApiError","httpStatus":500,"severity":"error","filePath":"apps/app/src/app/lib/den.ts","lineNumber":3501,"sourceCode":"        baseUrls,\n        \"/v1/plugins\",\n        {\n          method: \"POST\",\n          token,\n          organizationId: orgId,\n          body: {\n            name: input.name,\n            description: input.description ?? null,\n            components: input.components,\n            orgWide: input.orgWide === true,\n            ...(marketplaceId ? { marketplaceId } : {}),\n          },\n        },\n      );\n      const item = isRecord(payload) && isRecord(payload.item) ? payload.item : null;\n      const pluginId = item && typeof item.id === \"string\" ? item.id : null;\n      if (!pluginId) {\n        throw new DenApiError(500, \"invalid_plugin_payload\", \"Plugin was created but no id was returned.\");\n      }\n      return pluginId;\n    },\n\n    async getBillingStatus(options: { includePortal?: boolean; includeInvoices?: boolean } = {}): Promise<DenBillingSummary> {\n      const params = new URLSearchParams();\n      if (options.includePortal === false) {\n        params.set(\"excludePortal\", \"1\");\n      }\n      if (options.includeInvoices === false) {\n        params.set(\"excludeInvoices\", \"1\");\n      }\n\n      const path = params.size > 0 ? `/v1/workers/billing?${params.toString()}` : \"/v1/workers/billing\";\n      const payload = await requestJson<unknown>(baseUrls, path, {\n        method: \"GET\",\n        token,\n      });","sourceCodeStart":3483,"sourceCodeEnd":3519,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/app/lib/den.ts#L3483-L3519","documentation":"DenApiError thrown by createOrgPlugin in apps/app/src/app/lib/den.ts when POST /v1/plugins returned success but the response did not contain payload.item.id as a string — i.e. the plugin was created server-side yet the client could not extract its id. The message explicitly flags the awkward state that the resource exists but its identifier is unusable.","triggerScenarios":"POST /v1/plugins returns 200/201 with a body that is not an object, lacks an `item` object, or whose item.id is missing/non-string (e.g. numeric id, an error envelope with 200, or an older server returning the plugin at the top level instead of nested under item).","commonSituations":"Server version that nests or names the created resource differently than this client expects; an intermediary (proxy, service worker, mock) swallowing the body; duplicate-name handling on the server returning a 200 status with a message payload instead of the created item; a self-hosted Den deployment drifting from the client's schema.","solutions":["Log the raw POST /v1/plugins response body and check whether item.id exists or the shape differs (e.g. plugin at top level)","Verify the plugin was actually created by listing plugins afterward; if it exists, recover the id from the list instead of re-creating","Align client and server versions — schema drift on the create-plugin response is the usual cause","Check that no proxy/mock/interceptor strips or wraps the response body","Retry once only after confirming the plugin was not created, to avoid duplicate plugins"],"exampleFix":"// before\nconst item = isRecord(payload) && isRecord(payload.item) ? payload.item : null;\nconst pluginId = item && typeof item.id === \"string\" ? item.id : null;\nif (!pluginId) {\n  throw new DenApiError(500, \"invalid_plugin_payload\", \"Plugin was created but no id was returned.\");\n}\n// after\ncaller-side handling of the created-but-no-id state:\ntry {\n  const id = await client.createOrgPlugin(orgId, input);\n} catch (err) {\n  if (err instanceof DenApiError && err.code === \"invalid_plugin_payload\") {\n    const plugins = await client.listOrgPlugins(orgId); // reconcile instead of blind retry\n  }\n}","handlingStrategy":"try-catch","validationCode":"// validate input before POST /v1/plugins\nif (!input.name || input.name.trim().length === 0) {\n  throw new Error(\"Plugin name is required before creation\");\n}\nif (!Array.isArray(input.components) || input.components.length === 0) {\n  throw new Error(\"At least one component is required\");\n}","typeGuard":"function hasCreatedPluginId(v: unknown): v is { item: { id: string } } {\n  return (\n    typeof v === \"object\" && v !== null && \"item\" in v &&\n    typeof (v as { item?: unknown }).item === \"object\" && (v as { item?: unknown }).item !== null &&\n    typeof ((v as { item: { id?: unknown } }).item).id === \"string\"\n  );\n}","tryCatchPattern":"try {\n  const pluginId = await client.createOrgPlugin(orgId, input);\n} catch (err) {\n  if (err instanceof DenApiError && err.code === \"invalid_plugin_payload\") {\n    // plugin may exist: reconcile via list, never blind-retry the POST\n    const plugins = await client.listOrgPlugins(orgId);\n  } else { throw err; }\n}","preventionTips":["Never retry createOrgPlugin on this error without first checking whether the plugin was created","Confirm the server's create-plugin response shape (item.id) matches the client version","Log the raw POST response during development","Guard against proxies/mocks intercepting POST /v1/plugins","Catch on error code invalid_plugin_payload and reconcile rather than surfacing a generic failure"],"tags":["plugins","api-response","schema-mismatch","idempotency","den-api"],"backgroundTag":"invalid-api-response-shape","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}