nexu-io/open-design · error · ConnectorServiceError

CONNECTOR_SAFETY_DENIED

CONNECTOR_SAFETY_DENIED

Error message

connector tool is not auto-approved read-only by current safety policy

What it means

Thrown by ConnectorService.execute() with HTTP 403 when the connector's effective approval policy (the strictest of definition.minimumApproval, tool.safety.approval, and runtimeSafetyForTool(tool).approval) is not 'auto', OR the tool's runtime side effect is not 'read'. This is the read-only auto-approval gate: only fully-auto read-only tools may run unattended through execute(). details carries approvalPolicy and the full runtime safety object.

Source

Thrown at apps/daemon/src/connectors/service.ts:796

        connectorId: request.connectorId,
        expectedAccountLabel: request.expectedAccountLabel,
        currentAccountLabel: connector.accountLabel ?? null,
      });
    }
    if (!definition.allowedToolNames.includes(request.toolName)) {
      throw new ConnectorServiceError('CONNECTOR_TOOL_NOT_FOUND', 'connector tool is not allowed', 404, {
        connectorId: request.connectorId,
        toolName: request.toolName,
      });
    }
    const tool = definition.tools.find((candidate) => candidate.name === request.toolName);
    if (!tool) {
      throw new ConnectorServiceError('CONNECTOR_TOOL_NOT_FOUND', 'connector tool not found', 404);
    }
    const runtimeSafety = runtimeSafetyForTool(tool);
    const effectiveApproval = stricterApproval(stricterApproval(definition.minimumApproval, tool.safety.approval), runtimeSafety.approval);
    if (effectiveApproval !== 'auto' || runtimeSafety.sideEffect !== 'read') {
      throw new ConnectorServiceError('CONNECTOR_SAFETY_DENIED', 'connector tool is not auto-approved read-only by current safety policy', 403, {
        connectorId: request.connectorId,
        toolName: request.toolName,
        approvalPolicy: effectiveApproval ?? null,
        safety: { ...runtimeSafety },
      });
    }
    try {
      assertJsonSchemaMatches(request.input, tool.inputSchemaJson);
    } catch (error) {
      throw new ConnectorServiceError('CONNECTOR_INPUT_SCHEMA_MISMATCH', error instanceof Error ? error.message : String(error), 400, {
        connectorId: request.connectorId,
        toolName: request.toolName,
      });
    }

    this.enforceRunLimits(context);

    let providerOutput: BoundedJsonObject;

View on GitHub (pinned to 5be4028344)

Solutions

  1. Obtain explicit user approval for the tool first (the manual approval flow), then retry — do not try to bypass by lowering the policy.
  2. Switch to a read-only equivalent tool if one exists in allowedToolNames.
  3. If the tool is genuinely read-only but misclassified, correct tool.safety / the runtime safety mapping in the catalog, not at the call site.

Example fix

// before: auto-invoking a tool that may mutate
await execute({ connectorId, toolName: 'gmail_send', input }, ctx);

// after: route mutating tools through the approval flow
const safety = runtimeSafetyForTool(tool);
if (safety.sideEffect !== 'read' || tool.safety.approval !== 'auto') {
  await requestUserApproval(connectorId, toolName, input);
}
await execute({ connectorId, toolName, input }, ctx);
Defensive patterns

Strategy: try-catch

Validate before calling

const tool = def.tools.find(t => t.name === request.toolName);
const safety = runtimeSafetyForTool(tool);
const approval = stricterApproval(stricterApproval(def.minimumApproval, tool.safety.approval), safety.approval);
if (approval !== 'auto' || safety.sideEffect !== 'read') {
  // route to manual approval flow
  await requestUserApproval(request);
}
await connectorService.execute(request, context);

Type guard

function isAutoReadOnly(def: ConnectorCatalogDefinition, tool: ConnectorCatalogToolDefinition): boolean {
  const safety = runtimeSafetyForTool(tool);
  const approval = stricterApproval(stricterApproval(def.minimumApproval, tool.safety.approval), safety.approval);
  return approval === 'auto' && safety.sideEffect === 'read';
}

Try / catch

try { await connectorService.execute(request, context); }
catch (e) {
  if (e instanceof ConnectorServiceError && e.code === 'CONNECTOR_SAFETY_DENIED') {
    // surface to user for explicit approval; never auto-retry
  } else throw e;
}

Prevention

When it happens

Trigger: A tool whose definition or runtime safety requires manual approval (e.g. a write/mutating action like sending email) is invoked through the auto-execute path; or a tool marked read-only at the catalog level resolves to a non-read sideEffect via runtimeSafetyForTool.

Common situations: An agent tried to call a mutating connector tool (send, delete, update) without prior user approval; a connector's minimumApproval was raised from 'auto' to 'manual' by policy; runtimeSafetyForTool flagged a tool as side-effecting despite a stale catalog marking.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/0183eb4b8cf7d84c. Report an issue: GitHub.