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
- Re-authorize the connector so fresh credentials are sealed for the current instanceId/environment/subject/companyId (run the connector connect/claim flow again).
- 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.
- Purge cached/stored sealed credentials for the connector so the next claim mints new, correctly-bound ones.
- 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
- Never copy sealed credentials or the DB between instances/environments; always re-authorize per instance.
- Keep instanceId and environment stable across restarts; if they must change, plan a credential re-mint.
- After changing connector provider definitions or cloud accounts, purge cached envelopes and re-run the connect flow.
- Log the binding fields (instance, environment, provider) on mismatch to immediately identify which axis diverged.
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
- Access denied
- [plugin-kubernetes] egressMode=standard cannot enforce FQDN-
- UI parser path escapes package directory — skipping
- ${prefix}: "captureCredential" must be a function when prese
- OpenCode ${QUALIFIED_OPENCODE_VERSION} runner-owned executab
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/d1b473ec201d1324.
Report an issue: GitHub.