different-ai/openwork · error
workflow_requires_read_only_capabilities
workflow_requires_read_only_capabilities
Error message
workflow_requires_read_only_capabilities:${required.scriptPath} What it means
Thrown in createWorkflowVersion when a required capability exists in the manifest but its readOnly flag is not exactly true. Workflows may only depend on read-only capabilities, so any required tool that can mutate state blocks version registration.
Source
Thrown at ee/apps/den-api/src/workflows.ts:422
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,
rawSourceText: input.draft.code,
schemaVersion: "codemode-script-v1",View on GitHub (pinned to 2b7df46e8a)
Solutions
- Mark the required tool as readOnly: true in its plugin/capability definition and rebuild the manifest.
- Remove the non-read-only capability from the workflow's requiredCapabilities and re-test.
- If mutation is genuinely needed, use a workflow mechanism that permits write tools (if available) instead of the read-only path.
Example fix
// before (tool definition)
export const tool = defineTool({ name: "sendEmail", readOnly: false, ... })
// after
export const tool = defineTool({ name: "sendEmail", readOnly: true, ... }) // or drop it from the workflow Defensive patterns
Strategy: validation
Validate before calling
const built = await buildTools()
const notReadOnly = requiredCapabilities.filter(r => {
const e = built.manifest.find(m => m.scriptPath === r.scriptPath || m.scriptPath.replace(/^tools\./, "") === r.scriptPath)
return e && e.readOnly !== true
})
if (notReadOnly.length) throw new Error(`not read-only: ${notReadOnly.map(m => m.scriptPath)}`) Type guard
const isReadOnlyCap = (e: {readOnly?: boolean} | undefined): e is {readOnly: true} => e?.readOnly === true Try / catch
try {
await createWorkflowVersion(input)
} catch (e) {
if (String(e.message).startsWith("workflow_requires_read_only_capabilities:")) {
const path = e.message.split(":")[1]
// flip readOnly: true on that tool or remove it from the workflow
} else throw e
} Prevention
- Audit plugin tools for readOnly: true before declaring them in workflows.
- Gate CI on a check that all workflow-required capabilities are read-only.
- Watch plugin updates that change tool mutability.
When it happens
Trigger: Registering a workflow whose payload.parsed.requiredCapabilities includes a script whose manifest entry has readOnly !== true (false or undefined).
Common situations: A capability author changed a tool to be mutating after the workflow was authored; a plugin update flipped readOnly off; the workflow was originally validated against a read-only variant that later became write-capable.
Related errors
- workflow_capability_unavailable
- workflow_test_capability_mismatch
- workflow_test_receipt_already_used
- workflow_snapshot_not_found
- invalid_mcp_connection_payload
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/de8d5f5f9bd65814.
Report an issue: GitHub.