different-ai/openwork · error · ApiError
invalid_cloud_plugin
invalid_cloud_plugin
Error message
resolved cloud plugin is required
What it means
readCloudPluginResolved normalizes its input via normalizeCloudPluginResolved and throws this 400 invalid_cloud_plugin ApiError when the value does not match the expected CloudPluginResolved shape (plugin metadata plus memberships). It guards the boundary where a previously resolved cloud plugin payload is consumed (e.g. from a request or stored data).
Source
Thrown at apps/server/src/cloud-plugins.ts:171
const memberships = value.memberships.flatMap((entry) => {
if (!isRecord(entry) || typeof entry.configObjectId !== "string") return [];
const configObject = normalizeConfigObject(entry.configObject);
return [{ configObjectId: entry.configObjectId, ...(configObject ? { configObject } : {}) }];
});
return {
plugin: {
id: value.plugin.id,
name: value.plugin.name,
description: typeof value.plugin.description === "string" ? value.plugin.description : null,
updatedAt: typeof value.plugin.updatedAt === "string" ? value.plugin.updatedAt : null,
},
memberships,
};
}
export function readCloudPluginResolved(value: unknown): CloudPluginResolved {
const resolved = normalizeCloudPluginResolved(value);
if (!resolved) throw new ApiError(400, "invalid_cloud_plugin", "resolved cloud plugin is required");
return resolved;
}
function extractSkillBodyMarkdown(skillText: string): string {
const trimmed = skillText.trim();
if (!trimmed.startsWith("---")) return trimmed;
const rest = trimmed.slice(3);
const end = rest.indexOf("\n---");
if (end === -1) return trimmed;
return rest.slice(end + 4).replace(/^\s*\n?/, "");
}
function slugifyConfigObjectName(title: string, fallback: string): string {
let base = title
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");View on GitHub (pinned to 2b7df46e8a)
Solutions
- Pass the exact `resolved` object returned by the plugin bundle resolution endpoint (plugin + memberships), not the raw manifest.
- Re-run the resolve step to get a fresh, correctly shaped CloudPluginResolved instead of caching old payloads.
- Inspect the payload against the CloudPluginResolved shape: ensure plugin (id, name) and memberships exist.
- Migrate or delete stale stored cloud plugin records written by an older schema.
Example fix
// before
installCloudPlugin(manifestJson) // raw manifest
// after
const { resolved } = await resolveClaudePluginBundle(url);
installCloudPlugin(resolved); // normalized CloudPluginResolved Defensive patterns
Strategy: type-guard
Validate before calling
function looksLikeResolved(v: unknown): boolean {
const r = v as { plugin?: unknown; memberships?: unknown } | null;
return !!r && typeof r.plugin === "object" && r.plugin !== null && Array.isArray(r.memberships);
}
if (!looksLikeResolved(payload)) throw new Error("pass the resolved bundle, not the raw manifest"); Type guard
function isCloudPluginResolved(v: unknown): v is CloudPluginResolved {
const r = v as { plugin?: { id?: unknown; name?: unknown }; memberships?: unknown } | null;
return !!r && typeof r.plugin === "object" && r.plugin !== null &&
typeof r.plugin.id === "string" && typeof r.plugin.name === "string" &&
Array.isArray(r.memberships);
} Try / catch
try {
const resolved = readCloudPluginResolved(payload);
} catch (e) {
if (isApiError(e) && e.code === "invalid_cloud_plugin") {
// re-resolve the plugin bundle and retry with the fresh resolved object
}
} Prevention
- Always pass the `resolved` object from the bundle endpoint onward, never the raw manifest.
- Re-resolve rather than caching resolved payloads across API versions.
- Validate stored cloud plugin records after schema migrations.
- Type the payload as CloudPluginResolved end-to-end so shape drift is caught at compile time.
When it happens
Trigger: Calling the API/function with a payload that is not a normalized resolved plugin: missing plugin object, missing memberships array, passing the raw manifest instead of the resolved bundle, or truncated/stale data.
Common situations: Client passing the manifest or the raw GitHub tree response instead of the `resolved` object returned by the bundle endpoint; a persisted cloud plugin record from an older schema version; JSON shape drift after an API update.
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
- Invalid cloud provider sync response.
- Invalid cloud provider sync status response.
- invalid_plugin_manifest
- plugin_empty
- invalid_payload
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/c1cdfcfc629c4bd4.
Report an issue: GitHub.