Skip to content

Add "docker model code" command#264

Closed
ericcurtin wants to merge 1 commit intomainfrom
add-docker-model-code-command
Closed

Add "docker model code" command#264
ericcurtin wants to merge 1 commit intomainfrom
add-docker-model-code-command

Conversation

@ericcurtin
Copy link
Copy Markdown
Contributor

@ericcurtin ericcurtin commented Oct 19, 2025

First implemented with aider integration

Summary by Sourcery

Introduce a new 'code' command to the Docker CLI that launches the aider AI assistant in a container for interactive code editing and add associated helper functions and tests.

New Features:

  • Add docker model code command to run aider in an ephemeral Docker container for AI-driven code edits

Enhancements:

  • Implement prompt editing via the user’s configured editor when no prompt argument is provided
  • Add utilities to normalize model names, resolve the Model Runner URL, detect Docker Desktop, and remove elements from slices

Tests:

  • Add unit tests for command flags and argument validation
  • Add tests for prompt editor functionality and comment filtering
  • Add tests for getModelRunnerURL URL resolution logic
  • Add tests for the removeElement slice utility

First implemented with aider integration

Signed-off-by: Eric Curtin <eric.curtin@docker.com>
Copilot AI review requested due to automatic review settings October 19, 2025 17:20
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Oct 19, 2025

Reviewer's Guide

This PR introduces a new docker model code subcommand built with Cobra that launches the Aider AI assistant in an ephemeral Docker container to edit code within a Git repository, handling argument parsing, prompt acquisition (via args or opening an editor), model normalization, backend validation, runner initialization (including pulling models), and container execution with dynamic networking, and adds comprehensive tests for flags, args, prompt handling, utilities, and URL resolution.

Sequence diagram for docker model code command execution

sequenceDiagram
    actor User
    participant CLI as "docker model code"
    participant Git
    participant Editor
    participant ModelRunner
    participant Docker
    User->>CLI: Run 'docker model code [MODEL] [PROMPT]'
    CLI->>Git: Check if in git repo
    Git-->>CLI: Repo root path
    alt Prompt provided
        CLI->>CLI: Use provided prompt
    else No prompt provided
        CLI->>Editor: Open editor for prompt
        Editor-->>CLI: User writes prompt
    end
    CLI->>ModelRunner: Validate backend/model
    ModelRunner-->>CLI: Model info / pull if needed
    CLI->>Docker: Run aider container with args
    Docker-->>CLI: Aider runs, edits code
    CLI-->>User: Output results
Loading

Class diagram for new code command and related functions

classDiagram
    class CodeCmd {
        +backend : string
        +aiderImage : string
        +newCodeCmd()
        +PreRunE()
        +RunE()
        +Args()
    }
    class EditorUtils {
        +getPromptFromEditor() string
    }
    class ModelUtils {
        +getModelRunnerURL() string
        +validateBackend(backend)
        +ensureStandaloneRunnerAvailable()
        +pullModel()
        +isDockerDesktop() bool
        +removeElement(slice, element) []string
    }
    class DockerUtils {
        +runAiderInContainer(cmd, aiderImage, repoRoot, model, prompt, modelRunnerURL) error
    }
    class DesktopClient {
        +Inspect(model, bool)
    }
    CodeCmd --> EditorUtils : uses
    CodeCmd --> ModelUtils : uses
    CodeCmd --> DockerUtils : uses
    CodeCmd --> DesktopClient : uses
Loading

File-Level Changes

Change Details Files
Register new 'code' CLI command
  • Add newCodeCmd to root command’s subcommand list
cmd/cli/commands/root.go
Implement 'code' command logic with aider integration
  • Define Cobra command with Use/Short/Long/Example
  • PreRunE enforces Git repo context
  • RunE handles backend validation, model normalization, prompt acquisition (args or editor)
  • Resolve repository root and Model Runner URL
  • Initialize standalone runner or pull missing models
  • Build and execute Docker run command for Aider with network adjustments for Docker Desktop
cmd/cli/commands/code.go
Add unit tests for code command and helper functions
  • Assert presence and defaults of command flags
  • Validate argument requirements
  • Test getPromptFromEditor (including comment filtering)
  • Verify removeElement behavior
  • Verify getModelRunnerURL environment and path handling
cmd/cli/commands/code_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @ericcurtin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new docker model code command, enabling AI-assisted code editing directly from the command line. It integrates the aider tool, running it in a Docker container that connects to the Docker Model Runner. This allows developers to leverage AI models for code modifications within their local Git repositories, streamlining the development workflow by providing a flexible way to input prompts and ensuring compatibility across different Docker environments.

Highlights

  • New docker model code command: Introduces a new CLI command to integrate AI-assisted code editing using aider.
  • aider integration: The command runs aider within an ephemeral Docker container, allowing it to interact with the local codebase and the Docker Model Runner.
  • Prompt input flexibility: Users can provide a prompt directly as an argument or use a text editor (similar to git commit) if no prompt is specified, with comments automatically filtered out.
  • Model Runner integration: Automatically detects and uses the Docker Model Runner URL, handling model availability and pulling if necessary, and supports specifying a backend.
  • Docker Desktop compatibility: Adjusts Docker network settings for optimal operation within Docker Desktop environments, removing the --network host option when detected.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ericcurtin ericcurtin marked this pull request as draft October 19, 2025 17:20
@ericcurtin ericcurtin changed the title Add docker model code command Add "docker model code" command Oct 19, 2025
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

Blocking issues:

  • Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code. (link)

General comments:

  • Refactor the large RunE function in newCodeCmd into smaller, well-defined helpers to improve readability and make the logic easier to test.
  • In runAiderInContainer, avoid hardcoding the "openai/" model prefix—use the chosen backend flag to build the model reference dynamically.
  • Instead of relying on global desktopClient and modelRunner variables, inject these dependencies into the command for better modularity and unit testing.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Refactor the large RunE function in newCodeCmd into smaller, well-defined helpers to improve readability and make the logic easier to test.
- In runAiderInContainer, avoid hardcoding the "openai/" model prefix—use the chosen backend flag to build the model reference dynamically.
- Instead of relying on global desktopClient and modelRunner variables, inject these dependencies into the command for better modularity and unit testing.

## Individual Comments

### Comment 1
<location> `cmd/cli/commands/code_test.go:47` </location>
<code_context>
+	}
+}
+
+func TestGetPromptFromEditor(t *testing.T) {
+	// Save original environment
+	origEditor := os.Getenv("EDITOR")
</code_context>

<issue_to_address>
**suggestion (testing):** No test for editor failure or temp file creation error.

Add tests for editor launch failures and temp file creation errors to ensure getPromptFromEditor handles errors correctly.

Suggested implementation:

```golang
func TestGetPromptFromEditor(t *testing.T) {
	// Save original environment
	origEditor := os.Getenv("EDITOR")
	origVisual := os.Getenv("VISUAL")
	defer func() {
		os.Setenv("EDITOR", origEditor)
		os.Setenv("VISUAL", origVisual)
	}()

	// Test with a mock editor that writes to the temp file
	tmpDir := t.TempDir()
	mockEditor := filepath.Join(tmpDir, "mock-editor.sh")
	mockScript := `#!/bin/bash
`

	// ... existing test code for successful editor ...

	// Test editor launch failure
	t.Run("editor launch failure", func(t *testing.T) {
		os.Setenv("EDITOR", "/nonexistent-editor")
		_, err := getPromptFromEditor()
		if err == nil {
			t.Errorf("expected error when editor does not exist, got nil")
		}
	})

	// Test temp file creation failure by monkey-patching os.CreateTemp
	t.Run("temp file creation failure", func(t *testing.T) {
		origCreateTemp := osCreateTemp
		defer func() { osCreateTemp = origCreateTemp }()
		osCreateTemp = func(dir, pattern string) (*os.File, error) {
			return nil, fmt.Errorf("simulated temp file creation error")
		}
		_, err := getPromptFromEditor()
		if err == nil {
			t.Errorf("expected error when temp file creation fails, got nil")
		}
	})

```

You will need to refactor `getPromptFromEditor` to use a variable (e.g., `osCreateTemp`) for temp file creation, so it can be monkey-patched in the test. For example:

```go
var osCreateTemp = os.CreateTemp
```
And replace all `os.CreateTemp` calls in `getPromptFromEditor` with `osCreateTemp`.

Also, ensure `getPromptFromEditor` returns errors properly when editor launch or temp file creation fails.
</issue_to_address>

### Comment 2
<location> `cmd/cli/commands/code_test.go:26` </location>
<code_context>
+	}
+}
+
+func TestGetPromptFromEditorFiltersComments(t *testing.T) {
+	// Save original environment
+	origEditor := os.Getenv("EDITOR")
</code_context>

<issue_to_address>
**suggestion (testing):** No test for empty prompt after filtering comments.

Please add a test where only comment lines are provided, so the function's behavior with an empty prompt is verified.

```suggestion
}

func TestGetPromptFromEditorOnlyCommentsYieldsEmptyPrompt(t *testing.T) {
	// Save original environment
	origEditor := os.Getenv("EDITOR")
	origVisual := os.Getenv("VISUAL")
	defer func() {
		os.Setenv("EDITOR", origEditor)
		os.Setenv("VISUAL", origVisual)
	}()

	// Test with a mock editor that writes only comment lines to the temp file
	tmpDir := t.TempDir()
	mockEditor := filepath.Join(tmpDir, "mock-editor-comments.sh")
	mockScript := `#!/bin/bash
echo "# This is a comment" > $1
echo "# Another comment line" >> $1
`
	if err := os.WriteFile(mockEditor, []byte(mockScript), 0755); err != nil {
		t.Fatalf("failed to write mock editor script: %v", err)
	}
	os.Setenv("EDITOR", mockEditor)
	os.Setenv("VISUAL", "")

	prompt, err := getPromptFromEditor()
	if err != nil {
		t.Fatalf("getPromptFromEditor() returned error: %v", err)
	}

	if prompt != "" {
		t.Errorf("expected prompt to be empty after filtering comments, got: %q", prompt)
	}
}
```
</issue_to_address>

### Comment 3
<location> `cmd/cli/commands/code_test.go:174` </location>
<code_context>
+	}
+}
+
+func TestGetModelRunnerURL(t *testing.T) {
+	// Save original environment
+	origHost := os.Getenv("MODEL_RUNNER_HOST")
</code_context>

<issue_to_address>
**suggestion (testing):** Missing test for Docker Desktop and modelRunner context.

Please add tests for cases where modelRunner is set with various EngineKind values and for Docker Desktop context to ensure full coverage of getModelRunnerURL.

Suggested implementation:

```golang
	tests := []struct {
		name     string
		context  string
		engineKind string
		envHost  string
		expected string

```

```golang
	tests := []struct {
		name     string
		context  string
		engineKind string
		envHost  string
		expected string
	}{
		{
			name:     "Default context, no env",
			context:  "",
			engineKind: "",
			envHost:  "",
			expected: "http://localhost:8080",
		},
		{
			name:     "MODEL_RUNNER_HOST set",
			context:  "",
			engineKind: "",
			envHost:  "http://custom-host:9000",
			expected: "http://custom-host:9000",
		},
		{
			name:     "modelRunner context, EngineKind=local",
			context:  "modelRunner",
			engineKind: "local",
			envHost:  "",
			expected: "http://localhost:8080",
		},
		{
			name:     "modelRunner context, EngineKind=remote",
			context:  "modelRunner",
			engineKind: "remote",
			envHost:  "",
			expected: "https://modelrunner.example.com",
		},
		{
			name:     "Docker Desktop context",
			context:  "docker-desktop",
			engineKind: "",
			envHost:  "",
			expected: "http://host.docker.internal:8080",
		},
	}

```

```golang
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.envHost != "" {
				os.Setenv("MODEL_RUNNER_HOST", tt.envHost)
			} else {
				os.Unsetenv("MODEL_RUNNER_HOST")
			}
			result := getModelRunnerURL(tt.context, tt.engineKind)
			if result != tt.expected {
				t.Errorf("expected %v, got %v", tt.expected, result)
			}
		})
	}
}

```

1. You will need to update the signature of `getModelRunnerURL` to accept `context` and `engineKind` parameters, and update its implementation to handle these cases.
2. Ensure that the logic for Docker Desktop and modelRunner context with different EngineKind values is implemented in `getModelRunnerURL`.
3. If your codebase uses a different way to determine context/engineKind, adjust the test table and function calls accordingly.
</issue_to_address>

### Comment 4
<location> `cmd/cli/commands/code_test.go:10` </location>
<code_context>
+	"testing"
+)
+
+func TestCodeCommandFlags(t *testing.T) {
+	cmd := newCodeCmd()
+
</code_context>

<issue_to_address>
**suggestion (testing):** No test for flag value override via command line.

Please add a test that sets flags using command line arguments and checks that the override works as intended.

Suggested implementation:

```golang
func TestCodeCommandFlags(t *testing.T) {
	cmd := newCodeCmd()
}

// Test that command line flag overrides work as intended
func TestCodeCommandFlagOverride(t *testing.T) {
	cmd := newCodeCmd()

	// Example: Assume the command has a --path flag with default value "default/path"
	defaultPath := cmd.Flag("path").Value.String()
	if defaultPath != "default/path" {
		t.Fatalf("expected default path to be 'default/path', got '%s'", defaultPath)
	}

	// Simulate command line args to override the flag
	args := []string{"--path=overridden/path"}
	cmd.SetArgs(args)

	// Parse flags
	err := cmd.Execute()
	if err != nil {
		// If Execute() runs the command, use cmd.ParseFlags(args) or cmd.Flags().Parse(args) instead
		// depending on your CLI library. Adjust as needed.
		t.Fatalf("unexpected error executing command: %v", err)
	}

	// Check that the flag value was overridden
	overriddenPath := cmd.Flag("path").Value.String()
	if overriddenPath != "overridden/path" {
		t.Errorf("expected path to be overridden to 'overridden/path', got '%s'", overriddenPath)
	}
}

```

- You may need to adjust the flag name ("path") and default value ("default/path") to match your actual command's flags.
- If your CLI library does not support cmd.Flag(), use the appropriate method to access flag values.
- If cmd.Execute() runs the command logic, use cmd.ParseFlags(args) or cmd.Flags().Parse(args) to only parse flags.
- Ensure your command is set up to allow flag parsing without side effects during tests.
</issue_to_address>

### Comment 5
<location> `cmd/cli/commands/code.go:158` </location>
<code_context>
	editorCmd := exec.Command(editor, tmpPath)
</code_context>

<issue_to_address>
**security (go.lang.security.audit.dangerous-exec-command):** Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

if aiderImage != "paulgauthier/aider" {
t.Errorf("expected default aider-image to be 'paulgauthier/aider', got %q", aiderImage)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): No test for empty prompt after filtering comments.

Please add a test where only comment lines are provided, so the function's behavior with an empty prompt is verified.

Suggested change
}
}
func TestGetPromptFromEditorOnlyCommentsYieldsEmptyPrompt(t *testing.T) {
// Save original environment
origEditor := os.Getenv("EDITOR")
origVisual := os.Getenv("VISUAL")
defer func() {
os.Setenv("EDITOR", origEditor)
os.Setenv("VISUAL", origVisual)
}()
// Test with a mock editor that writes only comment lines to the temp file
tmpDir := t.TempDir()
mockEditor := filepath.Join(tmpDir, "mock-editor-comments.sh")
mockScript := `#!/bin/bash
echo "# This is a comment" > $1
echo "# Another comment line" >> $1
`
if err := os.WriteFile(mockEditor, []byte(mockScript), 0755); err != nil {
t.Fatalf("failed to write mock editor script: %v", err)
}
os.Setenv("EDITOR", mockEditor)
os.Setenv("VISUAL", "")
prompt, err := getPromptFromEditor()
if err != nil {
t.Fatalf("getPromptFromEditor() returned error: %v", err)
}
if prompt != "" {
t.Errorf("expected prompt to be empty after filtering comments, got: %q", prompt)
}
}

Comment thread cmd/cli/commands/code.go
tmpFile.Close()

// Open the editor
editorCmd := exec.Command(editor, tmpPath)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (go.lang.security.audit.dangerous-exec-command): Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

Source: opengrep

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new docker model code command that integrates aider (AI-powered code editing tool) to run within a Docker container and interact with Docker Model Runner for AI-assisted code editing.

  • Adds a new code command to the CLI with support for model selection and prompt input
  • Implements integration with aider running in an ephemeral Docker container
  • Provides editor-based prompt composition similar to git commit workflow

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
cmd/cli/commands/root.go Registers the new newCodeCmd() command in the root command structure
cmd/cli/commands/code.go Core implementation of the code command with aider integration and Docker container execution
cmd/cli/commands/code_test.go Comprehensive test suite covering command validation, editor integration, and utility functions

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread cmd/cli/commands/code.go
Comment on lines +63 to +68
if strings.TrimSpace(prompt) == "" {
fmt.Fprintf(os.Stderr, "Aborting coding task due to empty commit message.\n")
return nil
}
}
}
Copy link

Copilot AI Oct 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inner condition on line 63 will never be true since line 62 already checks if prompt is empty. The nested check for strings.TrimSpace(prompt) == \"\" is unreachable.

Suggested change
if strings.TrimSpace(prompt) == "" {
fmt.Fprintf(os.Stderr, "Aborting coding task due to empty commit message.\n")
return nil
}
}
}
fmt.Fprintf(os.Stderr, "Aborting coding task due to empty commit message.\n")
return nil
}
}
}

Copilot uses AI. Check for mistakes.
Comment thread cmd/cli/commands/code.go
Comment on lines +148 to +149
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit
Copy link

Copilot AI Oct 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instruction text refers to 'commit message' but this is for composing a coding prompt. Should be 'Please enter the prompt for your coding task' or similar.

Suggested change
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit
# Please enter the prompt for your coding task below. Lines starting
# with '#' will be ignored, and an empty prompt will abort the operation.

Copilot uses AI. Check for mistakes.
Comment thread cmd/cli/commands/code.go
return fmt.Errorf("failed to get prompt from editor: %w", err)
}
if strings.TrimSpace(prompt) == "" {
fmt.Fprintf(os.Stderr, "Aborting coding task due to empty commit message.\n")
Copy link

Copilot AI Oct 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message refers to 'commit message' but should refer to 'prompt' since this is about coding task prompts, not git commits.

Copilot uses AI. Check for mistakes.
Comment thread cmd/cli/commands/code.go
Comment on lines +215 to +216
func runAiderInContainer(cmd *cobra.Command, aiderImage, repoRoot, model, prompt, modelRunnerURL string) error {
model = "openai/" + model
Copy link

Copilot AI Oct 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coding the 'openai/' prefix for all models may not be appropriate. This should be conditional based on the backend or model configuration.

Suggested change
func runAiderInContainer(cmd *cobra.Command, aiderImage, repoRoot, model, prompt, modelRunnerURL string) error {
model = "openai/" + model
func runAiderInContainer(cmd *cobra.Command, aiderImage, repoRoot, model, prompt, modelRunnerURL string, backend string) error {
// Only prepend "openai/" if backend is openai
if backend == "openai" && !strings.HasPrefix(model, "openai/") {
model = "openai/" + model
}

Copilot uses AI. Check for mistakes.
Comment thread cmd/cli/commands/code.go
aiderArgs = removeElement(aiderArgs, "host")
}

cmd.Printf("Running aider with model %s %s...\n", model, aiderArgs)
Copy link

Copilot AI Oct 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format string expects two parameters but aiderArgs is a slice. This will print the slice address rather than the arguments. Consider using strings.Join(aiderArgs, \" \") or a different format.

Suggested change
cmd.Printf("Running aider with model %s %s...\n", model, aiderArgs)
cmd.Printf("Running aider with model %s %s...\n", model, strings.Join(aiderArgs, " "))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new docker model code command, which integrates with aider for AI-assisted coding. The implementation is solid and includes a good set of tests. My review focuses on a couple of areas for improvement: fixing a bug in the command-line prompt handling and refactoring the logic for manipulating command-line arguments to be more robust and efficient. These changes will improve the command's correctness and maintainability.

Comment thread cmd/cli/commands/code.go
Comment on lines +60 to +81
if argsLen > 1 {
prompt = strings.Join(args[1:], " ")
if prompt == "" {
if strings.TrimSpace(prompt) == "" {
fmt.Fprintf(os.Stderr, "Aborting coding task due to empty commit message.\n")
return nil
}
}
}

// If no prompt provided, open editor
if prompt == "" {
var err error
prompt, err = getPromptFromEditor()
if err != nil {
return fmt.Errorf("failed to get prompt from editor: %w", err)
}
if strings.TrimSpace(prompt) == "" {
fmt.Fprintf(os.Stderr, "Aborting coding task due to empty commit message.\n")
return nil
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic for handling the prompt argument is a bit convoluted and has a bug. If a user provides a prompt that consists only of whitespace (e.g., docker model code mymodel " "), the command will proceed with the whitespace prompt instead of opening the editor as intended. The logic can be simplified to correctly handle all cases: no prompt, an empty prompt, or a whitespace-only prompt.

if argsLen > 1 {
	prompt = strings.Join(args[1:], " ")
}

// If no prompt provided, or if it's all whitespace, open editor
if strings.TrimSpace(prompt) == "" {
	var err error
	prompt, err = getPromptFromEditor()
	if err != nil {
		return fmt.Errorf("failed to get prompt from editor: %w", err)
	}
	if strings.TrimSpace(prompt) == "" {
		fmt.Fprintf(os.Stderr, "Aborting coding task due to empty commit message.\n")
		return nil
	}
}

Comment thread cmd/cli/commands/code.go
Comment on lines +239 to +243
if isDockerDesktop() {
// Remove --network host and use Docker Desktop's DNS
aiderArgs = removeElement(aiderArgs, "--network")
aiderArgs = removeElement(aiderArgs, "host")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current method of removing the --network host arguments is fragile. It uses a generic removeElement function twice, which could unintentionally remove other arguments if they happen to be "host". A more robust approach is to specifically find and remove the --network host pair. This also allows removing the now-unnecessary removeElement helper function.

if isDockerDesktop() {
	// Remove --network host and use Docker Desktop's DNS
	newAiderArgs := make([]string, 0, len(aiderArgs))
	for i := 0; i < len(aiderArgs); i++ {
		if aiderArgs[i] == "--network" && i+1 < len(aiderArgs) && aiderArgs[i+1] == "host" {
			i++ // Skip both "--network" and "host"
		} else {
			newAiderArgs = append(newAiderArgs, aiderArgs[i])
		}
	}
	aiderArgs = newAiderArgs
}

Comment thread cmd/cli/commands/code.go
Comment on lines +275 to +284
// removeElement removes all occurrences of a string from a slice
func removeElement(slice []string, element string) []string {
result := []string{}
for _, item := range slice {
if item != element {
result = append(result, item)
}
}
return result
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With the suggested change to how command-line arguments are removed, this helper function is no longer needed and can be removed to simplify the code.

Comment on lines +124 to +172
func TestRemoveElement(t *testing.T) {
tests := []struct {
name string
slice []string
element string
expected []string
}{
{
name: "remove single occurrence",
slice: []string{"a", "b", "c"},
element: "b",
expected: []string{"a", "c"},
},
{
name: "remove multiple occurrences",
slice: []string{"a", "b", "c", "b"},
element: "b",
expected: []string{"a", "c"},
},
{
name: "element not present",
slice: []string{"a", "b", "c"},
element: "d",
expected: []string{"a", "b", "c"},
},
{
name: "empty slice",
slice: []string{},
element: "a",
expected: []string{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := removeElement(tt.slice, tt.element)
if len(result) != len(tt.expected) {
t.Errorf("expected length %d, got %d", len(tt.expected), len(result))
return
}
for i := range result {
if result[i] != tt.expected[i] {
t.Errorf("expected %v, got %v", tt.expected, result)
return
}
}
})
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the removeElement function is being removed, this corresponding test function is no longer needed and should also be removed.

@doringeman
Copy link
Copy Markdown
Contributor

First implemented with aider integration

Do you plan to make it generic or stick to aider?
By generic I mean something like docker model run --model <model> --assistant <container (e.g., aider)> <docker-run-flags>.

@ericcurtin ericcurtin closed this Oct 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants