{"record":{"id":"2ab7fd91f7200143","repo":"sigoden/aichat","slug":"no-tty-for-repl","errorCode":null,"errorMessage":"No TTY for REPL","messagePattern":"No TTY for REPL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/main.rs","lineNumber":188,"sourceCode":"    if let Some(name) = &cli.macro_name {\n        macro_execute(&config, name, text.as_deref(), abort_signal.clone()).await?;\n        return Ok(());\n    }\n    if cli.execute && !is_repl {\n        let input = create_input(&config, text, &cli.file, abort_signal.clone()).await?;\n        shell_execute(&config, &SHELL, input, abort_signal.clone()).await?;\n        return Ok(());\n    }\n    config.write().apply_prelude()?;\n    match is_repl {\n        false => {\n            let mut input = create_input(&config, text, &cli.file, abort_signal.clone()).await?;\n            input.use_embeddings(abort_signal.clone()).await?;\n            start_directive(&config, input, cli.code, abort_signal).await\n        }\n        true => {\n            if !*IS_STDOUT_TERMINAL {\n                bail!(\"No TTY for REPL\")\n            }\n            start_interactive(&config).await\n        }\n    }\n}\n\n#[async_recursion::async_recursion]\nasync fn start_directive(\n    config: &GlobalConfig,\n    input: Input,\n    code_mode: bool,\n    abort_signal: AbortSignal,\n) -> Result<()> {\n    let client = input.create_client()?;\n    let extract_code = !*IS_STDOUT_TERMINAL && code_mode;\n    config.write().before_chat_completion(&input)?;\n    let (output, tool_results) = if !input.stream() || extract_code {\n        call_chat_completions(","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/main.rs#L170-L206","documentation":"The REPL (interactive mode) was requested but stdout is not a terminal (`IS_STDOUT_TERMINAL` is false), so the interactive prompt cannot run. The library refuses to start an interactive session without a TTY.","triggerScenarios":"Running the binary with no prompt text (REPL mode) while stdout is piped/redirected, or inside a non-interactive environment (CI, cron, docker without -t).","commonSituations":"`mytool < input.txt` or `mytool | tee log` with no prompt argument; CI job invoking the CLI without a prompt; Docker container run without `-it`.","solutions":["Pass the prompt as a CLI argument/file (-f) so it runs in non-interactive mode","Allocate a TTY (e.g. `docker run -it`, run in a real terminal)","Check `is-terminal` detection if you believe a TTY exists"],"exampleFix":"// before (non-interactive)\nmytool | tee out.log\n// after\nmytool \"explain this code\" -f prompt.txt","handlingStrategy":"type-guard","validationCode":"if !atty::is(atty::Stream::Stdout) {\n    eprintln!(\"stdout is not a TTY; pass a prompt for non-interactive mode\");\n}","typeGuard":"fn has_tty() -> bool {\n    std::io::IsTerminal::is_terminal(&std::io::stdout())\n}","tryCatchPattern":"if let Err(e) = cli.run().await {\n    if e.to_string() == \"No TTY for REPL\" {\n        eprintln!(\"Provide a prompt argument or run inside a terminal\");\n    }\n}","preventionTips":["Always pass a prompt when running non-interactively (CI, scripts, pipes)","Use `docker run -it` when you need the REPL in containers","Check terminal detection before invoking the CLI without arguments"],"tags":["tty","repl","cli"],"backgroundTag":"missing-tty","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}