paperclipai/paperclip · warning · Error

`paperclipai connect` is interactive. For scripts, pass --ap

Error message

`paperclipai connect` is interactive. For scripts, pass --api-base/--api-key or use context set/token commands.

What it means

Thrown by connectWizard() at the very start when either process.stdin or process.stdout is not a TTY. The `paperclipai connect` command is an interactive wizard (uses @clack/prompts) that requires both streams to be interactive. In non-interactive contexts (pipes, CI, cron, redirected I/O) the prompts cannot render, so it aborts immediately.

Source

Thrown at cli/src/commands/client/connect.ts:58

      .command("connect")
      .description("Interactively connect the CLI as a board operator or agent")
      .option("--persona <persona>", "Persona to configure: board or agent")
      .option("--api-key-env-var-name <name>", "Env var name to store in the profile", "PAPERCLIP_API_KEY")
      .option("--token-name <name>", "Token label to create")
      .action(async (opts: ConnectOptions) => {
        try {
          const result = await connectWizard(opts);
          printOutput(result, { json: opts.json });
        } catch (err) {
          handleCommandError(err);
        }
      }),
  );
}

async function connectWizard(opts: ConnectOptions) {
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
    throw new Error("`paperclipai connect` is interactive. For scripts, pass --api-base/--api-key or use context set/token commands.");
  }

  p.intro(pc.bgCyan(pc.black(" paperclipai connect ")));

  const context = readContext(opts.context);
  const resolvedProfile = resolveProfile(context, opts.profile);
  const initialApiBase = resolveApiBase(opts, resolvedProfile.profile);
  const apiBaseInput = await p.text({
    message: "Paperclip API base",
    initialValue: initialApiBase,
    placeholder: "http://localhost:3100",
  });
  assertNotCancelled(apiBaseInput);
  const apiBase = normalizeApiBase(String(apiBaseInput || initialApiBase));
  console.log(pc.dim(`Checking ${apiBase}/api/health ...`));
  await verifyHealth(apiBase);

  const boardLogin = await loginBoardCli({

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Use non-interactive alternatives: `paperclipai context set` and `paperclipai token` with --api-base/--api-key flags.
  2. Set PAPERCLIP_API_BASE and PAPERCLIP_API_KEY environment variables directly.
  3. Allocate a TTY for interactive runs (e.g. `docker run -it`, `ssh -t`).

Example fix

# before (non-interactive)
paperclipai connect < /dev/null
# after (non-interactive setup)
export PAPERCLIP_API_BASE=http://localhost:3100
export PAPERCLIP_API_KEY=$KEY
paperclipai context set --profile default --api-base "$PAPERCLIP_API_BASE"
Defensive patterns

Strategy: validation

Validate before calling

// Detect non-interactive context before calling connect, and use the non-interactive path.
function isInteractive(): boolean {
  return !!process.stdin.isTTY && !!process.stdout.isTTY;
}
if (!isInteractive()) {
  throw new Error("Non-interactive shell: use 'paperclipai context set' + env vars instead of 'connect'.");
}

Type guard

function isTtyContext(): boolean {
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
}

Prevention

When it happens

Trigger: Running `paperclipai connect` inside CI, Docker with no TTY, a shell script with piped stdin/stdout, a cron job, or any non-interactive subshell; piping output to a file or another command.

Common situations: Automated provisioning scripts that call `connect`; Docker containers run without `-it`; SSH non-interactive commands; build pipelines.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/1181743c3b07ef49. Report an issue: GitHub.