{"record":{"id":"afe203d0efe824d4","repo":"paperclipai/paperclip","slug":"prefix-oncomplete-must-be-a-function-when-pr","errorCode":null,"errorMessage":"${prefix}: \"onComplete\" must be a function when present.","messagePattern":"(.+?): \"onComplete\" must be a function when present\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/login-capability.ts","lineNumber":145,"sourceCode":"      `${prefix}: \"sandboxTransport\" must be one of ${ADAPTER_LOGIN_SANDBOX_TRANSPORTS.join(\", \")}.`,\n    );\n  }\n  if (!isOneOf(ADAPTER_LOGIN_TIMEOUT_POLICIES, cap.timeoutPolicy)) {\n    throw new Error(\n      `${prefix}: \"timeoutPolicy\" must be one of ${ADAPTER_LOGIN_TIMEOUT_POLICIES.join(\", \")}.`,\n    );\n  }\n  if (typeof cap.getCommand !== \"function\") {\n    throw new Error(`${prefix}: \"getCommand\" must be a function.`);\n  }\n  if (typeof cap.parsePrompt !== \"function\") {\n    throw new Error(`${prefix}: \"parsePrompt\" must be a function.`);\n  }\n  if (cap.captureCredential !== undefined && typeof cap.captureCredential !== \"function\") {\n    throw new Error(`${prefix}: \"captureCredential\" must be a function when present.`);\n  }\n  if (cap.onComplete !== undefined && typeof cap.onComplete !== \"function\") {\n    throw new Error(`${prefix}: \"onComplete\" must be a function when present.`);\n  }\n  if (\n    cap.completionClaim !== undefined &&\n    !isOneOf(ADAPTER_LOGIN_COMPLETION_CLAIMS, cap.completionClaim)\n  ) {\n    throw new Error(\n      `${prefix}: \"completionClaim\" must be one of ${ADAPTER_LOGIN_COMPLETION_CLAIMS.join(\", \")} when present.`,\n    );\n  }\n}\n\n/**\n * Validates the optional login capability of an adapter module. The function is\n * a no-op when the module declares no login capability. It throws a clear error\n * when the module declares a malformed capability, so the loader fails closed.\n */\nexport function validateAdapterLoginCapability(mod: {\n  type?: unknown;","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/packages/adapter-utils/src/login-capability.ts#L127-L163","documentation":"Thrown by assertValidAdapterLoginCapability when the OPTIONAL member onComplete is present but not a function. onComplete(ctx: AdapterLoginCompletionContext) => Promise<void> runs after a successful login to record non-secret completion state (e.g. the stored session id); absent is allowed, present-but-not-a-function means a malformed capability.","triggerScenarios":"An adapter declares onComplete: undefined-adjacent junk such as a string description, a boolean, or a plain object of handlers; validateAdapterLoginCapability rejects the module at load.","commonSituations":"Declaring onComplete as metadata ({ storedSessionId: true }) instead of the hook function; converting a capability to JSON and back; mixing up completionClaim (a string) and onComplete (the hook).","solutions":["Drop onComplete if the adapter needs no post-login bookkeeping.","Or provide an async function receiving AdapterLoginCompletionContext and recording the non-secret state; it must carry no credential byte.","If you meant to declare that a session id was stored, use completionClaim: \"storedSessionId\" instead.","Type the capability as AdapterLoginCapability so non-function values fail to compile."],"exampleFix":"// before\nloginCapability = { ..., onComplete: { storedSessionId: true } };\n\n// after\nloginCapability = {\n  ...,\n  onComplete: async (ctx) => { if (ctx.storedSessionId) await persistSessionRef(ctx.storedSessionId); },\n  completionClaim: \"storedSessionId\",\n};","handlingStrategy":"type-guard","validationCode":"const cap = capability as { onComplete?: unknown };\nconst ok = cap.onComplete === undefined || typeof cap.onComplete === \"function\";","typeGuard":"import type { AdapterLoginCapability } from \"@paperclipai/adapter-utils\";\n\nfunction onCompleteOk(v: unknown): v is AdapterLoginCapability {\n  const c = v as { onComplete?: unknown };\n  return c.onComplete === undefined || typeof c.onComplete === \"function\";\n}","tryCatchPattern":"try {\n  assertValidAdapterLoginCapability(cap, adapterType);\n} catch (error) {\n  throw new Error(`optional member onComplete malformed: ${String(error)}`);\n}","preventionTips":["onComplete is the async hook; completionClaim is the string — do not swap them.","Keep the hook non-secret: record ids, never credentials.","Omit it entirely when no post-login bookkeeping is needed."],"tags":["adapter","login-capability","validation","typescript"],"backgroundTag":"schema-validation-failed","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-21T17:58:32.592Z","contentChangedAt":"2026-08-21T17:58:32.592Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}