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
- 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
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
- 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
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
- ${prefix}: "panelMode" must be one of ${ADAPTER_LOGIN_PANEL_
- ${prefix}: "sandboxTransport" must be one of ${ADAPTER_LOGIN
- ${prefix}: "timeoutPolicy" must be one of ${ADAPTER_LOGIN_TI
- ${prefix}: "getCommand" must be a function.
- ${prefix}: "parsePrompt" must be a function.
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21).
Data as JSON: /api/errors/ecb038746aca6bf0.
Report an issue: GitHub.