atuinsh/atuin · error
failed to open editor
Error message
failed to open editor
What it means
`open_editor` spawns the configured editor as a child process to edit a temp script file. If the editor process exits with a non-zero status, the command assumes the edit failed and bails. Note: some editors (e.g. vim with `:cq`, or diff-conflicting tools) exit non-zero even after a successful edit.
Solutions
- Re-run the command and exit the editor normally (e.g. `:wq` in vim, not `:cq`).
- Check/change `$EDITOR` to a reliable editor: `export EDITOR=vim` (or nano/code --wait).
- Verify the editor command runs standalone on the file to confirm the non-zero exit source.
Example fix
# before export EDITOR='code' # returns immediately / non-zero without --wait # after export EDITOR='code --wait'
Defensive patterns
Strategy: fallback
Validate before calling
# verify the editor works and exits 0 before relying on it: $EDITOR /tmp/atuin-editor-check </dev/null >/dev/null 2>&1 \ || echo "warning: $EDITOR exits non-zero; set EDITOR to a stable editor"
Try / catch
if let Err(e) = open_editor(&path).await {
if e.to_string().contains("failed to open editor") {
eprintln!("editor exited non-zero; re-run and quit the editor normally (e.g. :wq)");
}
return Err(e);
} Prevention
- Set EDITOR to an editor that exits 0 on normal saves (vim/nano; `code --wait`).
- Never leave editors via `:cq` when scripting.
- Test wrapper-editor scripts return 0 on success.
When it happens
Trigger: Editing a new/existing script (e.g. `atuin scripts new`) where the editor command configured via `$EDITOR`/settings exits non-zero after the user saves.
Common situations: Vim terminated with `:cq` or a killed buffer; editor crashed; `$EDITOR` set to a wrapper script returning non-zero; terminal issue interrupting the child.
AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19).
Data as JSON: /api/errors/0a5ce3448f7c54bf.
Report an issue: GitHub.
Appendix: source
Thrown at crates/atuin/src/command/client/scripts.rs:144
// Create a temporary file
let temp_file = NamedTempFile::new()?;
let path = temp_file.into_temp_path();
// Write initial content to the temp file if provided
if let Some(content) = initial_content {
std::fs::write(&path, content)?;
}
// Open the file in the user's preferred editor
let editor_str = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
// Use shlex to safely split the string into shell-like parts.
let parts = shlex::split(&editor_str).ok_or_eyre("Failed to parse editor command")?;
let (command, args) = parts.split_first().ok_or_eyre("No editor command found")?;
let status = std::process::Command::new(command).args(args).arg(&path).status()?;
if !status.success() {
bail!("failed to open editor");
}
// Read back the edited content
let content = std::fs::read_to_string(&path)?;
path.close()?;
Ok(content)
}
// Helper function to execute a script and manage stdin/stdout/stderr
async fn execute_script(script_content: String, shebang: String) -> Result<i32> {
let mut session = execute_script_interactive(script_content, shebang)
.await
.expect("failed to execute script");
// Create a channel to signal when the process exits
let (exit_tx, mut exit_rx) = tokio::sync::oneshot::channel();
View on GitHub (pinned to 900007c480)