paperclipai/paperclip · error · Error

${prefix}: the capability must be an object.

Error message

${prefix}: the capability must be an object.

What it means

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.

Source

Thrown at packages/adapter-utils/src/login-capability.ts:116

function isOneOf<T extends readonly string[]>(values: T, candidate: unknown): candidate is T[number] {
  return typeof candidate === "string" && (values as readonly string[]).includes(candidate);
}

/**
 * Validates one login capability. The function fails closed: it throws a clear
 * error for a malformed shape. It checks each scalar field against its fixed
 * value set, checks each required function member, and checks each optional
 * member only when the member is present. `adapterType` names the adapter in the
 * error text.
 */
export function assertValidAdapterLoginCapability(
  value: unknown,
  adapterType: string,
): asserts value is AdapterLoginCapability {
  const prefix = `Adapter "${adapterType}" declares an invalid login capability`;
  if (typeof value !== "object" || value === null) {
    throw new Error(`${prefix}: the capability must be an object.`);
  }
  const cap = value as Record<string, unknown>;

  if (!isOneOf(ADAPTER_LOGIN_PANEL_MODES, cap.panelMode)) {
    throw new Error(
      `${prefix}: "panelMode" must be one of ${ADAPTER_LOGIN_PANEL_MODES.join(", ")}.`,
    );
  }
  if (!isOneOf(ADAPTER_LOGIN_SANDBOX_TRANSPORTS, cap.sandboxTransport)) {
    throw new Error(
      `${prefix}: "sandboxTransport" must be one of ${ADAPTER_LOGIN_SANDBOX_TRANSPORTS.join(", ")}.`,
    );
  }
  if (!isOneOf(ADAPTER_LOGIN_TIMEOUT_POLICIES, cap.timeoutPolicy)) {
    throw new Error(
      `${prefix}: "timeoutPolicy" must be one of ${ADAPTER_LOGIN_TIMEOUT_POLICIES.join(", ")}.`,
    );
  }

View on GitHub (pinned to a7e689b3c3)

Solutions

  1. Export a complete AdapterLoginCapability object (panelMode, sandboxTransport, timeoutPolicy, getCommand, parsePrompt, plus optional members)
  2. If the adapter needs no interactive login, omit the login capability entirely instead of exporting null
  3. Add a unit test that calls assertValidAdapterLoginCapability on the exported capability so load-time failures become build failures

Example fix

// before
export const loginCapability = null; // placeholder

// after: declare a complete capability, or omit it entirely for API-key-only adapters
export const loginCapability: AdapterLoginCapability = {
  panelMode: 'displayed_code',
  sandboxTransport: 'pseudo_terminal',
  timeoutPolicy: 'caller_bounded',
  getCommand: () => 'agent auth login',
  parsePrompt: (output) => parseUrlAndCode(output),
};
Defensive patterns

Strategy: type-guard

Validate before calling

if (adapter.loginCapability !== undefined && !isLoginCapabilityObject(adapter.loginCapability)) {
  // fail with a build/load-time message instead of an opaque runtime throw
  throw new Error(`Adapter ${adapter.type} exports a malformed loginCapability; remove it or complete it.`);
}
assertValidAdapterLoginCapability(adapter.loginCapability, adapter.type);

Type guard

import type { AdapterLoginCapability } from '@paperclipai/adapter-utils';

export function isLoginCapabilityObject(value: unknown): value is AdapterLoginCapability {
  if (typeof value !== 'object' || value === null) return false;
  const cap = value as Record<string, unknown>;
  return typeof cap.getCommand === 'function'
    && typeof cap.parsePrompt === 'function'
    && typeof cap.panelMode === 'string'
    && typeof cap.sandboxTransport === 'string'
    && typeof cap.timeoutPolicy === 'string';
}

Try / catch

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.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21). Data as JSON: /api/errors/ecb038746aca6bf0. Report an issue: GitHub.