paperclipai/paperclip · error · PaperclipCloudConnectorError

CONNECTOR_BINDING_MISMATCH

CONNECTOR_BINDING_MISMATCH

Error message

Paperclip Cloud credential binding did not match

What it means

In openCredentials (server/src/services/paperclip-cloud-connector.ts:303), after unsealing the connector credentials envelope, the code validates the credential bindings — instanceId, environment, subject, companyId, and provider must each match the current configuration and request context. Any mismatch throws PaperclipCloudConnectorError with code CONNECTOR_BINDING_MISMATCH. This prevents credentials minted for one instance/environment/subject/company/provider from being used in a different context (credential confusion / cross-tenant reuse).

Source

Thrown at server/src/services/paperclip-cloud-connector.ts:303

    const definition = connectorProfileDefinition(profile);
    const envelope = parseEnvelope(response.sealed, purpose, definition.provider, profile);
    const credentials = unseal(
      envelope,
      sealKey,
      config.instanceId,
      config.environment,
      definition.provider,
      profile,
      definition.scopes,
    );
    if (
      credentials.instanceId !== config.instanceId
      || credentials.environment !== config.environment
      || credentials.subject !== subject
      || credentials.companyId !== companyId
      || credentials.provider !== definition.provider
    ) {
      throw new PaperclipCloudConnectorError("Paperclip Cloud credential binding did not match", "CONNECTOR_BINDING_MISMATCH");
    }
    if (credentials.profile !== profile) {
      throw new PaperclipCloudConnectorError("Paperclip Cloud connector profile binding did not match", "CONNECTOR_BINDING_MISMATCH");
    }
    if (!sameStringSet(credentials.scopes, definition.scopes)) {
      throw new PaperclipCloudConnectorError("Paperclip Cloud scope grant did not match", "REAUTHORIZATION_REQUIRED");
    }
    return credentials;
  }

  return {
    async getInstanceStatus(): Promise<"active" | "suspended" | "removed"> {
      let response: ConnectorResponse;
      try {
        response = await call("status", {
          subject: "instance-status",
          companyId: "instance-status",
        });

View on GitHub (pinned to 01ad858492)

Solutions

  1. Re-authorize the connector so fresh credentials are sealed for the current instanceId/environment/subject/companyId (run the connector connect/claim flow again).
  2. Check server config (instanceId, environment) against the values used when the credentials were minted; align config or re-mint credentials — never hand-edit sealed envelopes.
  3. Purge cached/stored sealed credentials for the connector so the next claim mints new, correctly-bound ones.
  4. If this happens across restarts, verify the signing/seal key and instance identity are stable and not rotating, and that companyId/subject arguments at the call site match the connector registration.

Example fix

// before (config drifted from minted credentials)
const config = { instanceId: "inst-b", environment: "production" }; // creds were sealed for inst-a/dev
await connector.claim(...); // CONNECTOR_BINDING_MISMATCH
// after
// re-run the connector authorization flow for inst-b/production so a new envelope is sealed,
// or restore the original config the credentials were bound to:
const config = { instanceId: "inst-a", environment: "development" };
await connector.claim(...); // bindings match
Defensive patterns

Strategy: try-catch

Validate before calling

// before claim/refresh, confirm local config matches the registered connector binding
if (credentials && (credentials.instanceId !== config.instanceId || credentials.environment !== config.environment)) {
  await reauthorizeConnector(); // mint fresh credentials instead of failing later
}

Type guard

function isBoundToContext(
  c: { instanceId: string; environment: string; subject: string; companyId: string; provider: string },
  ctx: { instanceId: string; environment: string; subject: string; companyId: string; provider: string },
): boolean {
  return c.instanceId === ctx.instanceId && c.environment === ctx.environment
    && c.subject === ctx.subject && c.companyId === ctx.companyId && c.provider === ctx.provider;
}

Try / catch

try {
  const creds = await connector.claim(args);
} catch (err) {
  if (err instanceof PaperclipCloudConnectorError && err.code === "CONNECTOR_BINDING_MISMATCH") {
    await purgeStoredCredentials();
    return await connector.claim(await startAuthorizationFlow(args)); // re-mint bound credentials
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling claim() or refresh() where the unsealed credentials were issued for a different Paperclip instance id, environment, subject, companyId, or connector provider than the ones passed to openCredentials — e.g. the connector response's sealed envelope was created under another config.instanceId or the subject/companyId arguments changed since the credentials were minted.

Common situations: Restoring a database or copying sealed credentials between dev/staging/prod environments (environment or instanceId mismatch); pointing the instance at a different Paperclip Cloud account (subject/companyId mismatch); changing the connector provider definition while old credentials are still cached; shared cloud tenants across multiple self-hosted instances.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/d1b473ec201d1324. Report an issue: GitHub.