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
- Ensure an interactive TTY: run the profiling script directly in a real terminal.
- Skip interactive mode when there is no TTY: check sys.stdin.isatty() before starting the listener.
- 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
- Check sys.stdin.isatty() before interactive profiling.
- Run profiling scripts from a real terminal, not pipes/CI.
- Provide a non-interactive mode flag for automation.
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
- Cannot get terminal attributes: {e}
- Failed to start browser: {e}
- Profile not found: {profile_path}
- msvcrt module not available on this platform
- termios/tty/select modules not available on this platform
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/3785dbbbfd87c08f.
Report an issue: GitHub.