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

  1. Re-run the command and exit the editor normally (e.g. `:wq` in vim, not `:cq`).
  2. Check/change `$EDITOR` to a reliable editor: `export EDITOR=vim` (or nano/code --wait).
  3. 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

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)