# Goose Enhanced Code Editing Architecture: External Editor Integration Explained

> Explore Goose Enhanced Code Editing Architecture. Seamlessly integrate external editors for a streamlined coding workflow with temporary files and symlinks.

- Repository: [Block Open Source/goose](https://github.com/block/goose)
- Tags: architecture
- Published: 2026-04-05

---

**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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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).

### Symlink RAII Guard

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`](https://github.com/block/goose/blob/main/.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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/.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`](https://github.com/block/goose/blob/main/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:

```bash

# 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`](https://github.com/block/goose/blob/main/session/input.rs) at runtime.

### Implementing the Editor Launch Flow

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

```rust
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:

```json
{
  "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`](https://github.com/block/goose/blob/main/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:

```rust
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`](https://github.com/block/goose/blob/main/editor.rs)**: Contains `create_temp_file`, `SymlinkCleanup`, `launch_editor`, and `extract_user_input`—the core editor lifecycle management.
- **[`input.rs`](https://github.com/block/goose/blob/main/input.rs)**: Routes between line-editing and editor modes via `Config::get_goose_prompt_editor()` and `extract_recent_messages`.
- **[`output.rs`](https://github.com/block/goose/blob/main/output.rs)**: Renders tool requests through `render_text_editor_request` (lines 77-88).
- **[`export.rs`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/.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`](https://github.com/block/goose/blob/main/editor.rs) infrastructure as user prompts. The [`export.rs`](https://github.com/block/goose/blob/main/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`](https://github.com/block/goose/blob/main/session/editor.rs) lines 49-78.