{"record":{"id":"ecb038746aca6bf0","repo":"paperclipai/paperclip","slug":"prefix-the-capability-must-be-an-object","errorCode":null,"errorMessage":"${prefix}: the capability must be an object.","messagePattern":"(.+?): the capability must be an object\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/login-capability.ts","lineNumber":116,"sourceCode":"\nfunction isOneOf<T extends readonly string[]>(values: T, candidate: unknown): candidate is T[number] {\n  return typeof candidate === \"string\" && (values as readonly string[]).includes(candidate);\n}\n\n/**\n * Validates one login capability. The function fails closed: it throws a clear\n * error for a malformed shape. It checks each scalar field against its fixed\n * value set, checks each required function member, and checks each optional\n * member only when the member is present. `adapterType` names the adapter in the\n * error text.\n */\nexport function assertValidAdapterLoginCapability(\n  value: unknown,\n  adapterType: string,\n): asserts value is AdapterLoginCapability {\n  const prefix = `Adapter \"${adapterType}\" declares an invalid login capability`;\n  if (typeof value !== \"object\" || value === null) {\n    throw new Error(`${prefix}: the capability must be an object.`);\n  }\n  const cap = value as Record<string, unknown>;\n\n  if (!isOneOf(ADAPTER_LOGIN_PANEL_MODES, cap.panelMode)) {\n    throw new Error(\n      `${prefix}: \"panelMode\" must be one of ${ADAPTER_LOGIN_PANEL_MODES.join(\", \")}.`,\n    );\n  }\n  if (!isOneOf(ADAPTER_LOGIN_SANDBOX_TRANSPORTS, cap.sandboxTransport)) {\n    throw new Error(\n      `${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  }","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/packages/adapter-utils/src/login-capability.ts#L98-L134","documentation":"assertValidAdapterLoginCapability() validates an adapter's declared login capability and fails closed on a malformed shape. The first check requires the capability to be a non-null object; null, undefined, strings, numbers, booleans, and other primitives are rejected before any field checks (panelMode, sandboxTransport, timeoutPolicy, getCommand, parsePrompt, ...). Adapters with no interactive login (API-key-only vendors) should declare no capability at all rather than a null placeholder.","triggerScenarios":"The adapter loader calls assertValidAdapterLoginCapability on a package whose exported loginCapability is null/undefined or a primitive — e.g. `export const loginCapability = null` as a placeholder, a factory result exported wrongly, or a capability that arrived via JSON/structured clone so it lost its object shape.","commonSituations":"Adapter authors stubbing the capability with null 'for now'; exporting a function instead of its object result; copy-pasting an adapter and leaving a TODO value; serializing capabilities across boundaries that strip or replace objects.","solutions":["Export a complete AdapterLoginCapability object (panelMode, sandboxTransport, timeoutPolicy, getCommand, parsePrompt, plus optional members)","If the adapter needs no interactive login, omit the login capability entirely instead of exporting null","Add a unit test that calls assertValidAdapterLoginCapability on the exported capability so load-time failures become build failures"],"exampleFix":"// before\nexport const loginCapability = null; // placeholder\n\n// after: declare a complete capability, or omit it entirely for API-key-only adapters\nexport const loginCapability: AdapterLoginCapability = {\n  panelMode: 'displayed_code',\n  sandboxTransport: 'pseudo_terminal',\n  timeoutPolicy: 'caller_bounded',\n  getCommand: () => 'agent auth login',\n  parsePrompt: (output) => parseUrlAndCode(output),\n};","handlingStrategy":"type-guard","validationCode":"if (adapter.loginCapability !== undefined && !isLoginCapabilityObject(adapter.loginCapability)) {\n  // fail with a build/load-time message instead of an opaque runtime throw\n  throw new Error(`Adapter ${adapter.type} exports a malformed loginCapability; remove it or complete it.`);\n}\nassertValidAdapterLoginCapability(adapter.loginCapability, adapter.type);","typeGuard":"import type { AdapterLoginCapability } from '@paperclipai/adapter-utils';\n\nexport function isLoginCapabilityObject(value: unknown): value is AdapterLoginCapability {\n  if (typeof value !== 'object' || value === null) return false;\n  const cap = value as Record<string, unknown>;\n  return typeof cap.getCommand === 'function'\n    && typeof cap.parsePrompt === 'function'\n    && typeof cap.panelMode === 'string'\n    && typeof cap.sandboxTransport === 'string'\n    && typeof cap.timeoutPolicy === 'string';\n}","tryCatchPattern":"At adapter load time, wrap assertValidAdapterLoginCapability in try-catch and disable only the login capability (log the adapter name), so a malformed optional capability does not take down the whole adapter.","preventionTips":["Omit the login capability entirely for API-key-only adapters; never export null placeholders","Unit-test every adapter's exported capability with assertValidAdapterLoginCapability","Never pass capabilities through JSON/structured clone — function members do not survive"],"tags":["adapter","login","capability","validation","plugin"],"backgroundTag":"invalid-adapter-capability","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-21T17:58:32.592Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}