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.

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)

Solutions

  1. Grant the owner member (or one of their teams) an access grant on the config object or plugin.
  2. Promote the owner to an org admin role, which bypasses the grant check.
  3. 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

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

Related errors


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