different-ai/openwork · error

workflow_capability_unavailable

workflow_capability_unavailable

Error message

workflow_capability_unavailable:${required.scriptPath}

What it means

Thrown in createWorkflowVersion when a capability declared in payload.requiredCapabilities cannot be matched to an entry in the built tools manifest. The manifest is indexed by scriptPath (both 'tools.'-prefixed and stripped forms); if the path is missing or its capabilityName differs from what the workflow payload requires, the version is rejected. This guards against registering workflows that reference capabilities that no longer exist or were renamed.

Source

Thrown at ee/apps/den-api/src/workflows.ts:420

    throw new Error("workflow_matching_test_receipt_required")
  }

  const consumed = await db.select({ id: ConfigObjectVersionTable.id }).from(ConfigObjectVersionTable).where(and(
    eq(ConfigObjectVersionTable.organizationId, resource.configObject.organizationId),
    eq(ConfigObjectVersionTable.configObjectId, resource.configObject.id),
    eq(ConfigObjectVersionTable.sourceRevisionRef, receipt.id),
  )).limit(1)
  if (consumed[0]) throw new Error("workflow_test_receipt_already_used")

  const built = await input.buildTools()
  const manifestByPath = new Map(built.manifest.flatMap((entry) => [
    [entry.scriptPath, entry] as const,
    [entry.scriptPath.replace(/^tools\./, ""), entry] as const,
  ]))
  for (const required of payload.parsed.requiredCapabilities) {
    const current = manifestByPath.get(required.scriptPath)
    if (!current || current.capabilityName !== required.capabilityName) {
      throw new Error(`workflow_capability_unavailable:${required.scriptPath}`)
    }
    if (current.readOnly !== true) throw new Error(`workflow_requires_read_only_capabilities:${required.scriptPath}`)
  }
  for (const call of parseCodemodeToolCalls(receipt.tool_calls)) {
    if (!payload.parsed.requiredCapabilities.some((required) => {
      const normalized = call.name.replace(/^tools\./, "")
      return required.scriptPath === call.name || required.scriptPath.replace(/^tools\./, "") === normalized
    })) throw new Error(`workflow_test_capability_mismatch:${call.name}`)
  }

  const now = new Date()
  const configObjectVersionId = createDenTypeId("configObjectVersion")
  await db.transaction(async (tx) => {
    await tx.insert(ConfigObjectVersionTable).values({
      id: configObjectVersionId,
      organizationId: resource.configObject.organizationId,
      configObjectId: resource.configObject.id,
      normalizedPayloadJson: payload.value,

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Re-run the workflow test in the current environment so requiredCapabilities are regenerated against the current manifest.
  2. Update requiredCapabilities (or the workflow payload) to match the current scriptPath and capabilityName.
  3. Restore/reinstall the missing plugin capability or script referenced by the workflow.

Example fix

// before
requiredCapabilities: [{ scriptPath: "tools.search.web", capabilityName: "web-search-v1" }]
// after (match the current manifest entry)
requiredCapabilities: [{ scriptPath: "tools.search.webSearch", capabilityName: "webSearch" }]
Defensive patterns

Strategy: validation

Validate before calling

const built = await buildTools()
const paths = new Set(built.manifest.flatMap(m => [m.scriptPath, m.scriptPath.replace(/^tools\./, "")]))
const missing = requiredCapabilities.filter(r => !paths.has(r.scriptPath))
if (missing.length) throw new Error(`unavailable: ${missing.map(m => m.scriptPath)}`)

Type guard

const isAvailable = (m: {scriptPath: string; capabilityName: string}[], r: {scriptPath: string; capabilityName: string}) =>
  m.some(e => (e.scriptPath === r.scriptPath || e.scriptPath.replace(/^tools\./, "") === r.scriptPath.replace(/^tools\./, "")) && e.capabilityName === r.capabilityName)

Try / catch

try {
  await createWorkflowVersion(input)
} catch (e) {
  if (String(e.message).startsWith("workflow_capability_unavailable:")) {
    const path = e.message.split(":")[1]
    // reinstall/rename capability at `path` or refresh requiredCapabilities
  } else throw e
}

Prevention

When it happens

Trigger: Registering a workflow version whose parsed.requiredCapabilities references a scriptPath absent from input.buildTools() output, or whose capabilityName was changed/renamed since the test run was recorded.

Common situations: A tool/script was renamed or deleted after the workflow test ran; the payload's requiredCapabilities were hand-edited; deploying against an environment where a plugin capability is not installed.

Related errors


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