{"record":{"id":"0183eb4b8cf7d84c","repo":"nexu-io/open-design","slug":"connector-safety-denied","errorCode":"CONNECTOR_SAFETY_DENIED","errorMessage":"connector tool is not auto-approved read-only by current safety policy","messagePattern":"connector tool is not auto-approved read-only by current safety policy","errorType":"http","errorClass":"ConnectorServiceError","httpStatus":403,"severity":"error","filePath":"apps/daemon/src/connectors/service.ts","lineNumber":796,"sourceCode":"        connectorId: request.connectorId,\n        expectedAccountLabel: request.expectedAccountLabel,\n        currentAccountLabel: connector.accountLabel ?? null,\n      });\n    }\n    if (!definition.allowedToolNames.includes(request.toolName)) {\n      throw new ConnectorServiceError('CONNECTOR_TOOL_NOT_FOUND', 'connector tool is not allowed', 404, {\n        connectorId: request.connectorId,\n        toolName: request.toolName,\n      });\n    }\n    const tool = definition.tools.find((candidate) => candidate.name === request.toolName);\n    if (!tool) {\n      throw new ConnectorServiceError('CONNECTOR_TOOL_NOT_FOUND', 'connector tool not found', 404);\n    }\n    const runtimeSafety = runtimeSafetyForTool(tool);\n    const effectiveApproval = stricterApproval(stricterApproval(definition.minimumApproval, tool.safety.approval), runtimeSafety.approval);\n    if (effectiveApproval !== 'auto' || runtimeSafety.sideEffect !== 'read') {\n      throw new ConnectorServiceError('CONNECTOR_SAFETY_DENIED', 'connector tool is not auto-approved read-only by current safety policy', 403, {\n        connectorId: request.connectorId,\n        toolName: request.toolName,\n        approvalPolicy: effectiveApproval ?? null,\n        safety: { ...runtimeSafety },\n      });\n    }\n    try {\n      assertJsonSchemaMatches(request.input, tool.inputSchemaJson);\n    } catch (error) {\n      throw new ConnectorServiceError('CONNECTOR_INPUT_SCHEMA_MISMATCH', error instanceof Error ? error.message : String(error), 400, {\n        connectorId: request.connectorId,\n        toolName: request.toolName,\n      });\n    }\n\n    this.enforceRunLimits(context);\n\n    let providerOutput: BoundedJsonObject;","sourceCodeStart":778,"sourceCodeEnd":814,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/connectors/service.ts#L778-L814","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Obtain explicit user approval for the tool first (the manual approval flow), then retry — do not try to bypass by lowering the policy.","Switch to a read-only equivalent tool if one exists in allowedToolNames.","If the tool is genuinely read-only but misclassified, correct tool.safety / the runtime safety mapping in the catalog, not at the call site."],"exampleFix":"// before: auto-invoking a tool that may mutate\nawait execute({ connectorId, toolName: 'gmail_send', input }, ctx);\n\n// after: route mutating tools through the approval flow\nconst safety = runtimeSafetyForTool(tool);\nif (safety.sideEffect !== 'read' || tool.safety.approval !== 'auto') {\n  await requestUserApproval(connectorId, toolName, input);\n}\nawait execute({ connectorId, toolName, input }, ctx);","handlingStrategy":"try-catch","validationCode":"const tool = def.tools.find(t => t.name === request.toolName);\nconst safety = runtimeSafetyForTool(tool);\nconst approval = stricterApproval(stricterApproval(def.minimumApproval, tool.safety.approval), safety.approval);\nif (approval !== 'auto' || safety.sideEffect !== 'read') {\n  // route to manual approval flow\n  await requestUserApproval(request);\n}\nawait connectorService.execute(request, context);","typeGuard":"function isAutoReadOnly(def: ConnectorCatalogDefinition, tool: ConnectorCatalogToolDefinition): boolean {\n  const safety = runtimeSafetyForTool(tool);\n  const approval = stricterApproval(stricterApproval(def.minimumApproval, tool.safety.approval), safety.approval);\n  return approval === 'auto' && safety.sideEffect === 'read';\n}","tryCatchPattern":"try { await connectorService.execute(request, context); }\ncatch (e) {\n  if (e instanceof ConnectorServiceError && e.code === 'CONNECTOR_SAFETY_DENIED') {\n    // surface to user for explicit approval; never auto-retry\n  } else throw e;\n}","preventionTips":["Classify tools read-only vs mutating in the catalog so the agent can filter proactively.","Never bypass CONNECTOR_SAFETY_DENIED by retrying — it requires explicit user approval.","Keep runtimeSafetyForTool mappings aligned with the catalog's safety.approval."],"tags":["connector","safety","permissions","http-403"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}