Goose Enhanced Code Editing Architecture: External Editor Integration Explained

Goose extends its REPL CLI with a modular pipeline that launches external editors via temporary Markdown files, manages cleanup through RAII-guarded symlinks, and processes both user prompts and LLM-driven file modifications through the same integration layer.

The Goose CLI by Block supports an optional enhanced code editing capability that diverts input handling from the standard line-editing interface to external editors like vim, code, or nano. This architecture enables developers to compose complex prompts and review code changes within familiar editing environments while maintaining full conversation context and automatic resource cleanup.

Configuration and Input Routing

The system determines whether to activate editor integration through the goose_prompt_editor configuration setting. In crates/goose-cli/src/session/input.rs, the Config::get_goose_prompt_editor() function checks for this setting at lines 95-98. When configured, the CLI extracts recent conversation history using extract_recent_messages to provide context for the editing session, pulling the newest messages first to prime the external editor with relevant background.

Editor Session Management

The core orchestration resides in crates/goose-cli/src/session/editor.rs, which handles the complete lifecycle of external editor processes through four critical stages:

Temporary File Generation

The create_temp_file function (lines 9-27) generates a Markdown file with the prefix goose_prompt_*.md containing a static header, a "Your prompt:" section, and the recent conversation history. This structured template ensures users can distinguish between context (the conversation) and editable content (their new prompt).

To prevent orphaned temporary files, the SymlinkCleanup struct (lines 31-47) implements a deterministic cleanup pattern. It creates a symlink named .goose_prompt_temp.md pointing to the temporary file, then guarantees removal of this symlink through its Drop implementation—even if the program panics or the editor crashes unexpectedly.

Editor Launch and Monitoring

The launch_editor function (lines 49-78) splits the configured command string (e.g., "vim +/Your prompt") into executable and arguments, executes the process synchronously, and validates the exit status. This blocking call ensures the CLI waits for the user to complete their editing session before proceeding.

Content Extraction

After the editor closes, extract_user_input (lines 27-60, specifically the logic through line 60) parses the temporary file and strips everything before the # Your prompt: marker, discarding the conversation context section and returning only the user-authored content.

Tool Request Integration

When the LLM requests file operations, the architecture routes these through the same editor integration layer. In crates/goose-cli/src/session/output.rs, the render_text_editor_request function (lines 77-88) displays tool calls for write or edit operations, showing the target path and formatted content blocks.

The crates/goose-cli/src/session/export.rs file handles serialization of these requests (lines 118-124), matching tool names against "write" or "edit" and funneling them through editor::get_editor_input. This allows users to review and modify generated code before the system persists changes to disk, creating a ToolResponse that the LLM receives as confirmation.

The Complete Workflow Pipeline

The enhanced code editing architecture operates through a deterministic sequence:

  1. Configuration Check: The REPL loop invokes get_input, which queries Config::get_goose_prompt_editor() to determine whether to use standard line-editing or external editor mode.

  2. Context Assembly: extract_recent_messages retrieves conversation snippets to populate the temporary file template.

  3. File Preparation: create_temp_file writes the Markdown template to a temporary location, while SymlinkCleanup establishes the RAII-guarded symlink at .goose_prompt_temp.md.

  4. Editor Execution: launch_editor spawns the configured editor with arguments positioning the cursor at the "Your prompt" section.

  5. Input Processing: After editor exit, extract_user_input parses the file and returns a tuple (user_input, has_meaningful_content) to get_editor_input.

  6. CLI Integration: If content exists, the input is added to history and processed as a standard message; otherwise, the system falls back to line-editing.

  7. Tool Rendering: For LLM-generated file operations, render_text_editor_request displays the proposed changes, and export.rs serializes the request through the same temporary-file mechanism.

  8. Cleanup: The SymlinkCleanup destructor removes the temporary symlink regardless of success or failure status.

Code Implementation Examples

Configuring the External Editor

Set your preferred editor through the Goose configuration system:


# Configure vim as the external prompt editor

goose configure set goose_prompt_editor "vim"

# Or use VS Code with line positioning

goose configure set goose_prompt_editor "code --goto"

This value is stored in the global Config and accessed by session/input.rs at runtime.

Implementing the Editor Launch Flow

The following Rust pattern demonstrates how the CLI orchestrates the external editor session:

use crate::session::editor;

// Retrieve recent conversation for context
let recent = extract_recent_messages(&session, 10);

// Launch editor and extract input
let (prompt, has_content) = editor::get_editor_input("vim", &recent)?;

if has_content {
    // Process as standard user message
    session.handle_message(prompt).await?;
} else {
    // Fall back to line-editing interface
    return get_line_input();
}

Handling LLM Write Requests

When Goose receives a tool request to write code, it routes through the editor integration:

{
  "tool_call": {
    "name": "write",
    "arguments": {
      "path": "/tmp/fibonacci.js",
      "content": "function fibonacci(n) {\n  return n < 2 ? n : fibonacci(n-1) + fibonacci(n-2);\n}"
    }
  }
}

The export.rs module matches this tool name and invokes the editor workflow, allowing user review before the ToolResponse is generated.

RAII Cleanup Implementation

The symlink cleanup mechanism ensures resources are never leaked, even during panics:

use std::os::unix::fs::symlink;
use std::path::PathBuf;

// In crates/goose-cli/src/session/editor.rs
let temp_path = create_temp_file(&content)?;
let symlink_path = PathBuf::from(".goose_prompt_temp.md");

// Guard ensures cleanup on drop
let _guard = SymlinkCleanup::new(symlink_path.clone());
symlink(&temp_path, &symlink_path)?;

// Editor launches here...
// Symlink removed automatically when _guard drops

Key Source Files

The enhanced code editing capability spans four primary modules in the crates/goose-cli/src/session/ directory:

  • editor.rs: Contains create_temp_file, SymlinkCleanup, launch_editor, and extract_user_input—the core editor lifecycle management.
  • input.rs: Routes between line-editing and editor modes via Config::get_goose_prompt_editor() and extract_recent_messages.
  • output.rs: Renders tool requests through render_text_editor_request (lines 77-88).
  • export.rs: Serializes write/edit operations and matches tool names to trigger editor workflows (lines 118-124).

Summary

  • Goose implements enhanced code editing through an optional external editor pipeline configured via goose_prompt_editor.
  • The temporary file workflow uses Markdown templates with conversation context and a "Your prompt" extraction marker.
  • RAII-guarded symlinks in SymlinkCleanup prevent resource leaks even during unexpected failures or panics.
  • The same integration layer handles both user-authored prompts and LLM-generated write/edit tool requests.
  • All editor operations are synchronous and blocking, ensuring the CLI maintains conversational state consistency.

Frequently Asked Questions

How does Goose decide when to open an external editor versus using the standard REPL?

Goose checks the goose_prompt_editor configuration setting at the start of each input cycle in session/input.rs. If this value is set to a valid editor command (such as vim or code), the system routes input through editor::get_editor_input instead of the rustyline-based line editor. If the configuration is empty or the editor returns no meaningful content, it automatically falls back to the standard REPL interface.

What happens if the external editor crashes or the terminal session ends unexpectedly?

The SymlinkCleanup struct in session/editor.rs implements the Rust Drop trait to guarantee cleanup. Even if the editor crashes, the process receives a signal, or the program panics, the destructor removes the .goose_prompt_temp.md symlink when the guard variable goes out of scope. The temporary file itself is managed by the OS standard temporary directory cleanup policies.

Can I use the enhanced editing features for programmatic file modifications, not just prompts?

Yes. When the LLM generates a write or edit tool request, Goose routes these through the same editor.rs infrastructure as user prompts. The export.rs module matches these tool names and invokes get_editor_input, allowing you to review and modify generated code in your external editor before Goose persists the changes to disk and returns a ToolResponse to the conversation.

Which editors are compatible with Goose's enhanced editing capabilities?

Any command-line editor that accepts a file path and supports synchronous execution works with the launch_editor implementation. Common configurations include vim, nvim, nano, emacs, and code (VS Code). The editor command can include arguments, such as vim +/Your prompt to position the cursor at the specific section, parsed correctly by the command-splitting logic in session/editor.rs lines 49-78.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →