{"record":{"id":"3785dbbbfd87c08f","repo":"unclecode/crawl4ai","slug":"stdin-is-not-a-terminal","errorCode":null,"errorMessage":"stdin is not a terminal","messagePattern":"stdin is not a terminal","errorType":"exception","errorClass":"ImportError","httpStatus":null,"severity":"warning","filePath":"crawl4ai/browser_profiler.py","lineNumber":265,"sourceCode":"                self.logger.warning(f\"Error in Windows keyboard listener: {e}\", tag=tag)\n                # Continue trying instead of failing completely\n                await asyncio.sleep(0.1)\n                continue\n    \n    async def _listen_unix(self, user_done_event: asyncio.Event, check_browser_process, tag: str):\n        \"\"\"Unix/Linux/macOS keyboard listener using termios and select.\"\"\"\n        try:\n            import termios\n            import tty\n            import select\n        except ImportError:\n            raise ImportError(\"termios/tty/select modules not available on this platform\")\n        \n        # Get stdin file descriptor\n        try:\n            fd = sys.stdin.fileno()\n        except (AttributeError, OSError):\n            raise ImportError(\"stdin is not a terminal\")\n        \n        # Save original terminal settings\n        old_settings = None\n        try:\n            old_settings = termios.tcgetattr(fd)\n        except termios.error as e:\n            raise ImportError(f\"Cannot get terminal attributes: {e}\")\n        \n        try:\n            # Switch to non-canonical mode (cbreak mode)\n            tty.setcbreak(fd)\n            \n            while True:\n                try:\n                    # Use select to check if input is available (non-blocking)\n                    # Timeout of 0.5 seconds to periodically check browser process\n                    readable, _, _ = select.select([sys.stdin], [], [], 0.5)\n                    ","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/crawl4ai/browser_profiler.py#L247-L283","documentation":"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.","triggerScenarios":"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.","commonSituations":"CI pipelines accidentally launching interactive profiling; running scripts via subprocess without a TTY; cron jobs; pytest capturing stdin; Jupyter notebooks.","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."],"exampleFix":"# before\nawait profiler.create_profile(...)  # launched from CI, stdin piped -> ImportError\n\n# after\nimport sys\nif not sys.stdin.isatty():\n    raise SystemExit('interactive profiling requires a terminal')\nawait profiler.create_profile(...)","handlingStrategy":"validation","validationCode":"import sys\nif not sys.stdin.isatty():\n    raise OSError('interactive profiling requires a terminal on stdin')","typeGuard":"import sys\ndef has_interactive_stdin() -> bool:\n    try:\n        return sys.stdin.isatty()\n    except Exception:\n        return False","tryCatchPattern":null,"preventionTips":["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."],"tags":["browser-profiler","tty","interactive","environment"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}