{"record":{"id":"65d29f449723b896","repo":"paperclipai/paperclip","slug":"prefix-sandboxtransport-must-be-one-of-ada","errorCode":null,"errorMessage":"${prefix}: \"sandboxTransport\" must be one of ${ADAPTER_LOGIN_SANDBOX_TRANSPORTS.join(\", \")}.","messagePattern":"(.+?): \"sandboxTransport\" must be one of (.+?)\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/login-capability.ts","lineNumber":126,"sourceCode":" * 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  }\n  if (typeof cap.getCommand !== \"function\") {\n    throw new Error(`${prefix}: \"getCommand\" must be a function.`);\n  }\n  if (typeof cap.parsePrompt !== \"function\") {\n    throw new Error(`${prefix}: \"parsePrompt\" must be a function.`);\n  }\n  if (cap.captureCredential !== undefined && typeof cap.captureCredential !== \"function\") {\n    throw new Error(`${prefix}: \"captureCredential\" must be a function when present.`);\n  }\n  if (cap.onComplete !== undefined && typeof cap.onComplete !== \"function\") {","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/packages/adapter-utils/src/login-capability.ts#L108-L144","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nloginCapability = { sandboxTransport: \"pty\", ... };\n\n// after\nimport { type AdapterLoginSandboxTransport } from \"@paperclipai/adapter-utils\";\nloginCapability = { sandboxTransport: \"pseudo_terminal\" satisfies AdapterLoginSandboxTransport, ... };","handlingStrategy":"type-guard","validationCode":"import { ADAPTER_LOGIN_SANDBOX_TRANSPORTS } from \"@paperclipai/adapter-utils\";\n\nconst ok = (ADAPTER_LOGIN_SANDBOX_TRANSPORTS as readonly string[]).includes(capability.sandboxTransport);","typeGuard":"import { ADAPTER_LOGIN_SANDBOX_TRANSPORTS, type AdapterLoginCapability } from \"@paperclipai/adapter-utils\";\n\nfunction hasValidTransport(v: unknown): v is AdapterLoginCapability {\n  const c = v as Record<string, unknown>;\n  return (ADAPTER_LOGIN_SANDBOX_TRANSPORTS as readonly string[]).includes(c?.sandboxTransport as string);\n}","tryCatchPattern":"try {\n  validateAdapterLoginCapability(mod);\n} catch (error) {\n  logger.error({ err: error as Error }, \"adapter login capability rejected\");\n  throw error; // loader must fail closed\n}","preventionTips":["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."],"tags":["adapter","login-capability","validation","sandbox","typescript"],"backgroundTag":"invalid-enum-value","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-21T17:58:32.592Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}