paperclipai/paperclip · error · Error

${prefix}: "sandboxTransport" must be one of ${ADAPTER_LOGIN

Error message

${prefix}: "sandboxTransport" must be one of ${ADAPTER_LOGIN_SANDBOX_TRANSPORTS.join(", ")}.

What it means

Thrown by assertValidAdapterLoginCapability when the declared loginCapability.sandboxTransport is not "streamed_exec" or "pseudo_terminal". The transport tells the server which sandbox channel runs the login command: streamed_exec uses the plain streamed exec channel, pseudo_terminal allocates a real PTY because some CLI login flows emit no prompt over a pipe.

Source

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

 * 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(", ")}.`,
    );
  }
  if (typeof cap.getCommand !== "function") {
    throw new Error(`${prefix}: "getCommand" must be a function.`);
  }
  if (typeof cap.parsePrompt !== "function") {
    throw new Error(`${prefix}: "parsePrompt" must be a function.`);
  }
  if (cap.captureCredential !== undefined && typeof cap.captureCredential !== "function") {
    throw new Error(`${prefix}: "captureCredential" must be a function when present.`);
  }
  if (cap.onComplete !== undefined && typeof cap.onComplete !== "function") {

View on GitHub (pinned to a7e689b3c3)

Solutions

  1. Set sandboxTransport to "pseudo_terminal" when the login command needs an interactive terminal prompt, or "streamed_exec" when plain exec output is enough.
  2. Type the field as AdapterLoginSandboxTransport so invalid literals fail to compile.
  3. Add a capability-shape unit test using validateAdapterLoginCapability in the adapter package.
  4. Verify the adapter is built against the same @paperclipai/adapter-utils version whose ADAPTER_LOGIN_SANDBOX_TRANSPORTS it was written for.

Example fix

// before
loginCapability = { sandboxTransport: "pty", ... };

// after
import { type AdapterLoginSandboxTransport } from "@paperclipai/adapter-utils";
loginCapability = { sandboxTransport: "pseudo_terminal" satisfies AdapterLoginSandboxTransport, ... };
Defensive patterns

Strategy: type-guard

Validate before calling

import { ADAPTER_LOGIN_SANDBOX_TRANSPORTS } from "@paperclipai/adapter-utils";

const ok = (ADAPTER_LOGIN_SANDBOX_TRANSPORTS as readonly string[]).includes(capability.sandboxTransport);

Type guard

import { ADAPTER_LOGIN_SANDBOX_TRANSPORTS, type AdapterLoginCapability } from "@paperclipai/adapter-utils";

function hasValidTransport(v: unknown): v is AdapterLoginCapability {
  const c = v as Record<string, unknown>;
  return (ADAPTER_LOGIN_SANDBOX_TRANSPORTS as readonly string[]).includes(c?.sandboxTransport as string);
}

Try / catch

try {
  validateAdapterLoginCapability(mod);
} catch (error) {
  logger.error({ err: error as Error }, "adapter login capability rejected");
  throw error; // loader must fail closed
}

Prevention

When it happens

Trigger: An adapter exports loginCapability with sandboxTransport: "pty", "terminal", or omits the field; the loader calls validateAdapterLoginCapability during module load/registration and the assertion fails before the adapter becomes usable.

Common situations: Adapters for interactive CLI logins (OAuth device flows) mistakenly naming the transport after the vendor instead of the transport kind; capability objects assembled dynamically (records, env-driven config) that bypass literal typing; upgrades that changed the accepted value set.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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