{"record":{"id":"fc411bfbf9530e3d","repo":"sigoden/aichat","slug":"failed-to-init-rag-in-non-interactive-mode","errorCode":null,"errorMessage":"Failed to init rag in non-interactive mode","messagePattern":"Failed to init rag in non-interactive mode","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/rag/mod.rs","lineNumber":67,"sourceCode":"            embedding_model: self.embedding_model.clone(),\n            hnsw: self.data.build_hnsw(),\n            bm25: self.data.build_bm25(),\n            data: self.data.clone(),\n            last_sources: RwLock::new(None),\n        }\n    }\n}\n\nimpl Rag {\n    pub async fn init(\n        config: &GlobalConfig,\n        name: &str,\n        save_path: &Path,\n        doc_paths: &[String],\n        abort_signal: AbortSignal,\n    ) -> Result<Self> {\n        if !*IS_STDOUT_TERMINAL {\n            bail!(\"Failed to init rag in non-interactive mode\");\n        }\n        println!(\"⚙ Initializing RAG...\");\n        let (embedding_model, chunk_size, chunk_overlap) = Self::create_config(config)?;\n        let (reranker_model, top_k) = {\n            let config = config.read();\n            (config.rag_reranker_model.clone(), config.rag_top_k)\n        };\n        let data = RagData::new(\n            embedding_model.id(),\n            chunk_size,\n            chunk_overlap,\n            reranker_model,\n            top_k,\n            embedding_model.max_batch_size(),\n        );\n        let mut rag = Self::create(config, name, save_path, data)?;\n        let mut paths = doc_paths.to_vec();\n        if paths.is_empty() {","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/rag/mod.rs#L49-L85","documentation":"`Rag::init` builds a RAG index interactively (asking the user to pick embedding/reranker models and chunk parameters). In non-interactive mode there is no way to collect these choices, so it bails with 'Failed to init rag in non-interactive mode'.","triggerScenarios":"Calling `Rag::init` (with a name, save path, and doc paths) when `IS_STDOUT_TERMINAL` is false — e.g. piped stdout, CI, scripts.","commonSituations":"Initializing a RAG document store from a script or Docker container without a TTY; automation pipelines invoking RAG setup.","solutions":["Run the RAG init in an interactive terminal once; reuse the saved index non-interactively afterwards","Pre-create the RAG config file at save_path so init prompts are skipped","Patch/wrap so defaults are supplied programmatically instead of prompts"],"exampleFix":"// before (CI script)\nmytool --rag init docs/\n// after: init locally, then reuse\nmytool init-rag docs/   # in a TTY\nci_job: mytool --rag docs/ query \"...\"","handlingStrategy":"try-catch","validationCode":"if !std::io::IsTerminal::is_terminal(&std::io::stdout()) {\n    eprintln!(\"RAG init requires a TTY; run it interactively first\");\n}","typeGuard":"fn can_init_rag() -> bool {\n    std::io::IsTerminal::is_terminal(&std::io::stdout())\n}","tryCatchPattern":"match Rag::init(config, name, &save_path, &docs, signal).await {\n    Err(e) if e.to_string().contains(\"non-interactive mode\") => {\n        eprintln!(\"Initialize RAG in a TTY, then reuse the saved index\");\n    }\n    other => other?,\n}","preventionTips":["Run RAG init once interactively; reuse the saved index in automation","Pre-create the RAG config at save_path with model/chunk settings","Avoid invoking RAG init from CI or piped contexts"],"tags":["rag","tty","interactive"],"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"}