{"record":{"id":"fcdc9834fa52484a","repo":"paperclipai/paperclip","slug":"app-slug-connectionmethod-key-oauth-requires-ownershipmodes","errorCode":null,"errorMessage":"${app.slug}/${connectionMethod.key}: oauth requires ownershipModes","messagePattern":"(.+?)/(.+?): oauth requires ownershipModes","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/ingest-app-definitions.mjs","lineNumber":1593,"sourceCode":"    !app.name ||\n    !Array.isArray(app.methods) ||\n    app.methods.length === 0\n  )\n    throw new Error(`${app.slug || \"unknown\"}: invalid AppDefinition`);\n  for (const connectionMethod of app.methods) {\n    if (\n      connectionMethod.auth === \"api_key\" &&\n      !connectionMethod.keyPlacement &&\n      (connectionMethod.purpose ?? \"tool\") !== \"channel\"\n    )\n      throw new Error(\n        `${app.slug}/${connectionMethod.key}: tool api_key requires keyPlacement`,\n      );\n    if (\n      connectionMethod.auth === \"oauth\" &&\n      connectionMethod.ownershipModes.length === 0\n    )\n      throw new Error(\n        `${app.slug}/${connectionMethod.key}: oauth requires ownershipModes`,\n      );\n    for (const connectionField of [\n      ...(connectionMethod.tenantFields ?? []),\n      ...(connectionMethod.extensionFields ?? []),\n      ...(connectionMethod.credentialFields ?? []),\n    ])\n      if (\n        connectionField.required &&\n        connectionField.type !== \"checkbox\" &&\n        !connectionField.placeholder\n      )\n        throw new Error(\n          `${app.slug}/${connectionMethod.key}/${connectionField.key}: required field needs placeholder`,\n        );\n  }\n};\nconst captureFiles = definitionsOnly ? [] : fs","sourceCodeStart":1575,"sourceCodeEnd":1611,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/ingest-app-definitions.mjs#L1575-L1611","documentation":"validateApp() requires every oauth connection method to declare a non-empty ownershipModes array — which parties can own the token (e.g. 'customer', 'dcr'). The ownership model drives connection UI and storage, so an oauth method without it is incomplete and rejected.","triggerScenarios":"A method with auth === \"oauth\" has ownershipModes undefined/null or ownershipModes.length === 0. This typically means the method was constructed manually instead of via the method() helper, which auto-fills ownershipModes for oauth.","commonSituations":"Hand-writing an oauth method object without ownershipModes; overriding the helper output with a spread that sets ownershipModes: []; refactoring that renamed the field (e.g. singular ownershipMode) leaving it empty.","solutions":["Add ownershipModes to the oauth method, e.g. ownershipModes: [\"customer\"] or [\"customer\", \"dcr\"].","Prefer building the method with the method() helper, which sets ownershipModes: [\"customer\", \"dcr\"] automatically when auth is \"oauth\".","If truly not oauth, change auth to the correct value (e.g. \"api_key\") so the oauth validation branch does not apply."],"exampleFix":"// before\n{ key: \"acme-mcp\", transport: \"mcp_remote\", auth: \"oauth\", ownershipModes: [] }\n// after\n{ key: \"acme-mcp\", transport: \"mcp_remote\", auth: \"oauth\", ownershipModes: [\"customer\", \"dcr\"] }","handlingStrategy":"validation","validationCode":"for (const m of app.methods) {\n  if (m.auth === 'oauth' && (!Array.isArray(m.ownershipModes) || m.ownershipModes.length === 0)) {\n    throw new Error(`${app.slug}/${m.key}: oauth methods need non-empty ownershipModes`);\n  }\n}","typeGuard":"const hasOwnershipModes = (m) => m.auth !== 'oauth' || (Array.isArray(m.ownershipModes) && m.ownershipModes.length > 0);","tryCatchPattern":null,"preventionTips":["Build oauth methods only through the method() helper, which fills ownershipModes automatically.","Avoid spreads that override the helper defaults with empty arrays.","Type the method shape so ownershipModes is required when auth is 'oauth'."],"tags":["validation","oauth","connection","ingest"],"backgroundTag":"missing-required-config-field","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}