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
- Set sandboxTransport to "pseudo_terminal" when the login command needs an interactive terminal prompt, or "streamed_exec" when plain exec output is enough.
- Type the field as AdapterLoginSandboxTransport so invalid literals fail to compile.
- Add a capability-shape unit test using validateAdapterLoginCapability in the adapter package.
- 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
- Pick pseudo_terminal only when the login CLI genuinely needs a TTY prompt; prefer streamed_exec otherwise.
- Use the AdapterLoginSandboxTransport type on the field.
- Re-run adapter capability tests whenever you bump @paperclipai/adapter-utils versions.
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
- ${prefix}: "panelMode" must be one of ${ADAPTER_LOGIN_PANEL_
- ${prefix}: "timeoutPolicy" must be one of ${ADAPTER_LOGIN_TI
- ${prefix}: "getCommand" must be a function.
- ${prefix}: "parsePrompt" must be a function.
- ${prefix}: "captureCredential" must be a function when prese
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21).
Data as JSON: /api/errors/65d29f449723b896.
Report an issue: GitHub.