{"record":{"id":"462a1ffa5a04e81e","repo":"xai-org/grok-build","slug":"pty-write-channel-closed","errorCode":null,"errorMessage":"PTY write channel closed","messagePattern":"PTY write channel closed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codegen/ptyctl/src/session.rs","lineNumber":219,"sourceCode":"            generation_rx,\n            generation_tx: generation_tx_weak,\n            raw_tail,\n            _shutdown_tx: Some(shutdown_tx),\n            output_tx,\n        })\n    }\n\n    /// Send keystrokes using vim notation (e.g. `\"<C-c>\"`, `\"hello<CR>\"`).\n    pub async fn send_keys(&self, notation: &str) -> Result<()> {\n        let bytes = keys::parse_keys(notation)?;\n        self.send_bytes(&bytes).await\n    }\n\n    /// Send raw bytes to the PTY.\n    pub async fn send_bytes(&self, bytes: &[u8]) -> Result<()> {\n        self.pty_write_tx\n            .send(bytes.to_vec())\n            .map_err(|_| anyhow::anyhow!(\"PTY write channel closed\"))\n    }\n\n    /// Read screen content as plain text.\n    pub async fn screen(&self, opts: &ScreenOpts) -> ScreenOutput {\n        let term = self.terminal.lock().await;\n        term.screen_content(opts)\n    }\n\n    /// Read screen content with style information.\n    pub async fn screen_styled(&self, opts: &ScreenOpts) -> Vec<StyledLine> {\n        let term = self.terminal.lock().await;\n        term.screen_styled(opts)\n    }\n\n    /// Read screen content as HTML.\n    pub async fn screen_html(&self, opts: &ScreenOpts) -> String {\n        let term = self.terminal.lock().await;\n        term.screen_html(opts)","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/ptyctl/src/session.rs#L201-L237","documentation":"Session::send_bytes sends raw input to the PTY through a tokio mpsc channel (pty_write_tx) consumed by the writer task. This error is thrown when that send fails, meaning the receiver end is dropped — the PTY's write task (or the whole session runtime) has shut down, so keystrokes can no longer be delivered.","triggerScenarios":"Calling send_bytes (directly or via send_keys) after the PTY process exited or the session was closed/dropped, causing the writer task that owns pty_write_rx to finish and drop the receiver.","commonSituations":"A long-running automation script that keeps typing after the shell/command exited; races where a `wait`/kill on the PTY process completes just before a final write; sending input to a session ID that was already terminated elsewhere.","solutions":["Check the PTY/session is still alive before writing; treat this error as 'session ended' and stop sending input","Track child-process exit (or a session-closed signal) and close your input loop when it fires","If you need the session, recreate it (spawn a new PTY session) instead of writing to the dead one","Retry is pointless for this error — the channel never reopens; handle it as terminal in your error path"],"exampleFix":"// before\nfor key in keys { session.send_keys(key).await?; } // may fail if PTY exited\n// after\nif !session.is_alive().await { return Err(anyhow!(\"session ended before input was sent\")); }\nfor key in keys { session.send_keys(key).await?; }","handlingStrategy":"try-catch","validationCode":"async fn can_send(session: &Session) -> bool {\n    !session.is_closed() // or equivalent liveness check exposed by the session\n}","typeGuard":null,"tryCatchPattern":"match session.send_bytes(input).await {\n    Err(e) if e.to_string() == \"PTY write channel closed\" => {\n        // session terminated; stop the input loop and finalize\n        tracing::info!(\"pty ended, stopping input\");\n    }\n    other => other?,\n}","preventionTips":["Stop writing input as soon as the PTY child process exits; join the wait handle with the input loop","Use a select! over child-exit and input channels so writes are cancelled on exit","Treat this error as terminal — never retry send_bytes after channel closure","Drain pending input only while the session reports alive"],"tags":["pty","channel-closed","rust","tokio"],"backgroundTag":"channel-closed","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}