unclecode/crawl4ai · warning · ImportError

stdin is not a terminal

Error message

stdin is not a terminal

What it means

Inside _listen_unix, after the termios imports succeed, sys.stdin.fileno() raising AttributeError (stdin replaced by a non-file object) or OSError (no backing fd) is converted to ImportError('stdin is not a terminal'). Interactive keypress listening fundamentally requires a real stdin terminal fd.

Source

Thrown at crawl4ai/browser_profiler.py:265

                self.logger.warning(f"Error in Windows keyboard listener: {e}", tag=tag)
                # Continue trying instead of failing completely
                await asyncio.sleep(0.1)
                continue
    
    async def _listen_unix(self, user_done_event: asyncio.Event, check_browser_process, tag: str):
        """Unix/Linux/macOS keyboard listener using termios and select."""
        try:
            import termios
            import tty
            import select
        except ImportError:
            raise ImportError("termios/tty/select modules not available on this platform")
        
        # Get stdin file descriptor
        try:
            fd = sys.stdin.fileno()
        except (AttributeError, OSError):
            raise ImportError("stdin is not a terminal")
        
        # Save original terminal settings
        old_settings = None
        try:
            old_settings = termios.tcgetattr(fd)
        except termios.error as e:
            raise ImportError(f"Cannot get terminal attributes: {e}")
        
        try:
            # Switch to non-canonical mode (cbreak mode)
            tty.setcbreak(fd)
            
            while True:
                try:
                    # Use select to check if input is available (non-blocking)
                    # Timeout of 0.5 seconds to periodically check browser process
                    readable, _, _ = select.select([sys.stdin], [], [], 0.5)
                    

View on GitHub (pinned to 7e80152142)

Solutions

  1. Ensure an interactive TTY: run the profiling script directly in a real terminal.
  2. Skip interactive mode when there is no TTY: check sys.stdin.isatty() before starting the listener.
  3. In CI/tests, mock the done-event or use a non-interactive profiling API that waits on the browser process only.

Example fix

# before
await profiler.create_profile(...)  # launched from CI, stdin piped -> ImportError

# after
import sys
if not sys.stdin.isatty():
    raise SystemExit('interactive profiling requires a terminal')
await profiler.create_profile(...)
Defensive patterns

Strategy: validation

Validate before calling

import sys
if not sys.stdin.isatty():
    raise OSError('interactive profiling requires a terminal on stdin')

Type guard

import sys
def has_interactive_stdin() -> bool:
    try:
        return sys.stdin.isatty()
    except Exception:
        return False

Prevention

When it happens

Trigger: Running the interactive profiling flow where stdin is piped/redirected (cat file | script), under nohup, in CI, in an IDE runner that replaces sys.stdin, or when pythonw hides the console. Also sys.stdin swapped with io.StringIO by test frameworks.

Common situations: CI pipelines accidentally launching interactive profiling; running scripts via subprocess without a TTY; cron jobs; pytest capturing stdin; Jupyter notebooks.

Related errors


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/3785dbbbfd87c08f. Report an issue: GitHub.