different-ai/openwork · error
automation_saved_script_forbidden
automation_saved_script_forbidden
Error message
automation_saved_script_forbidden
What it means
Thrown in validateWorkflowAutomationAction when the automation's owner member is not an admin and holds no access grant — via direct membership grants or team grants — for either the referenced config object (saved script) or the referenced plugin, per resolvePluginArchGrantRole. Only owners with at least one such grant may automate the saved script.
Solutions
- Grant the owner member (or one of their teams) an access grant on the config object or plugin.
- Promote the owner to an org admin role, which bypasses the grant check.
- Choose a different owner who already has access to the saved script/plugin.
Example fix
// before
await createAutomation({ ownerMemberId: viewerId, configObjectId, pluginId })
// after
await db.insert(ConfigObjectAccessGrantTable).values({ orgMembershipId: viewerId, configObjectId, role: "editor" })
await createAutomation({ ownerMemberId: viewerId, configObjectId, pluginId }) Defensive patterns
Strategy: validation
Validate before calling
const isOwnerGranted = resolvePluginArchGrantRole({ memberId: ownerMemberId, teamIds, grants: configObjectGrants }) !== null
|| resolvePluginArchGrantRole({ memberId: ownerMemberId, teamIds, grants: pluginGrants }) !== null
if (!isAdmin && !isOwnerGranted) throw new Error("would fail: automation_saved_script_forbidden") Try / catch
try {
await createAutomation(input)
} catch (e) {
if (e.message === "automation_saved_script_forbidden") {
// add a ConfigObject/Plugin access grant for the owner or pick an admin owner
} else throw e
} Prevention
- Check owner grants (direct + team) before saving automations.
- Avoid revoking grants while dependent automations exist.
- Prefer admin owners for org-wide automations.
When it happens
Trigger: Creating/updating an automation whose ownerMemberId lacks both ConfigObjectAccessGrantTable and PluginAccessGrantTable coverage (directly or via TeamMemberTable teams) for the script's config object / plugin.
Common situations: Automation reassigned to a member without plugin access; team membership changed removing the needed team grant; a grant revoked while the automation edit was pending.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- automation_owner_inactive
- automation_saved_script_input_invalid
- automation_saved_script_version_invalid
- automation_saved_script_version_not_found
- Clipboard is not available in this browser.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/757de13b1b51b4dc.
Report an issue: GitHub.
Appendix: source
Thrown at ee/apps/den-api/src/workflows.ts:552
}).from(ConfigObjectAccessGrantTable).where(and(
eq(ConfigObjectAccessGrantTable.organizationId, organizationId),
eq(ConfigObjectAccessGrantTable.configObjectId, configObjectId),
)),
db.select({
orgMembershipId: PluginAccessGrantTable.orgMembershipId,
orgWide: PluginAccessGrantTable.orgWide,
removedAt: PluginAccessGrantTable.removedAt,
role: PluginAccessGrantTable.role,
teamId: PluginAccessGrantTable.teamId,
}).from(PluginAccessGrantTable).where(and(
eq(PluginAccessGrantTable.organizationId, organizationId),
eq(PluginAccessGrantTable.pluginId, pluginId),
)),
])
const grantInput = { memberId: ownerMemberId, teamIds: teams.map((team) => team.id) }
if (!resolvePluginArchGrantRole({ ...grantInput, grants: configObjectGrants })
&& !resolvePluginArchGrantRole({ ...grantInput, grants: pluginGrants })) {
throw new Error("automation_saved_script_forbidden")
}
}
const rows = await db.select({ version: ConfigObjectVersionTable })
.from(ConfigObjectVersionTable)
.innerJoin(ConfigObjectTable, and(
eq(ConfigObjectTable.id, ConfigObjectVersionTable.configObjectId),
eq(ConfigObjectTable.organizationId, organizationId),
eq(ConfigObjectTable.objectType, "workflow"),
eq(ConfigObjectTable.status, "active"),
isNull(ConfigObjectTable.deletedAt),
))
.innerJoin(PluginConfigObjectTable, and(
eq(PluginConfigObjectTable.configObjectId, ConfigObjectTable.id),
eq(PluginConfigObjectTable.pluginId, pluginId),
isNull(PluginConfigObjectTable.removedAt),
))
.where(and(
eq(ConfigObjectVersionTable.id, configObjectVersionId),View on GitHub (pinned to 2b7df46e8a)