Skip to content

Add GitHub Action for golangci-lint #250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0

Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Lint

on:
workflow_dispatch:
pull_request:
push:
branches:
- main

permissions:
contents: read
pull-requests: read

jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for merge_group events with only-new-issues

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: golangci-lint
uses: golangci/[email protected]
timeout-minutes: 15
with:
version: v2.1
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0

Expand Down
4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ to assist developers in writing, debugging, and understanding code directly from
RunE: func(cmd *cobra.Command, args []string) error {
// If the help flag is set, show the help message
if cmd.Flag("help").Changed {
cmd.Help()
_ = cmd.Help()
return nil
}
if cmd.Flag("version").Changed {
Expand Down Expand Up @@ -303,7 +303,7 @@ func init() {
rootCmd.Flags().BoolP("quiet", "q", false, "Hide spinner in non-interactive mode")

// Register custom validation for the format flag
rootCmd.RegisterFlagCompletionFunc("output-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
_ = rootCmd.RegisterFlagCompletionFunc("output-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return format.SupportedFormats, cobra.ShellCompDirectiveNoFileComp
})
}
23 changes: 16 additions & 7 deletions internal/app/lsp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import (
"github.com/opencode-ai/opencode/internal/lsp/watcher"
)

// contextKey is a custom type for context keys to avoid collisions
type contextKey string

const (
serverNameKey contextKey = "serverName"
)

func (app *App) initLSPClients(ctx context.Context) {
cfg := config.Get()

Expand All @@ -25,7 +32,7 @@ func (app *App) initLSPClients(ctx context.Context) {
func (app *App) createAndStartLSPClient(ctx context.Context, name string, command string, args ...string) {
// Create a specific context for initialization with a timeout
logging.Info("Creating LSP client", "name", name, "command", command, "args", args)

// Create the LSP client
lspClient, err := lsp.NewClient(ctx, command, args...)
if err != nil {
Expand All @@ -36,13 +43,15 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman
// Create a longer timeout for initialization (some servers take time to start)
initCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()

// Initialize with the initialization context
_, err = lspClient.InitializeLSPClient(initCtx, config.WorkingDirectory())
if err != nil {
logging.Error("Initialize failed", "name", name, "error", err)
// Clean up the client to prevent resource leaks
lspClient.Close()
if closeErr := lspClient.Close(); closeErr != nil {
logging.Error("Failed to close LSP client after initialization failure", "name", name, "error", closeErr)
}
return
}

Expand All @@ -57,13 +66,13 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman
}

logging.Info("LSP client initialized", "name", name)

// Create a child context that can be canceled when the app is shutting down
watchCtx, cancelFunc := context.WithCancel(ctx)

// Create a context with the server name for better identification
watchCtx = context.WithValue(watchCtx, "serverName", name)
watchCtx = context.WithValue(watchCtx, serverNameKey, name)

// Create the workspace watcher
workspaceWatcher := watcher.NewWorkspaceWatcher(lspClient)

Expand Down
6 changes: 5 additions & 1 deletion internal/completions/files-folders.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ func (cg *filesAndFoldersContextGroup) getFiles(query string) ([]string, error)
if err != nil {
return nil, fmt.Errorf("failed to get rg stdout pipe: %w", err)
}
defer rgPipe.Close()
defer func() {
if closeErr := rgPipe.Close(); closeErr != nil {
logging.Warn("Failed to close rg pipe", "error", closeErr)
}
}()

cmdFzf.Stdin = rgPipe
var fzfOut bytes.Buffer
Expand Down
6 changes: 4 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,13 +416,15 @@ func readConfig(err error) error {
// mergeLocalConfig loads and merges configuration from the local directory.
func mergeLocalConfig(workingDir string) {
local := viper.New()
local.SetConfigName(fmt.Sprintf(".%s", appName))
local.SetConfigName(".opencode")
local.SetConfigType("json")
local.AddConfigPath(workingDir)

// Merge local config if it exists
if err := local.ReadInConfig(); err == nil {
viper.MergeConfigMap(local.AllSettings())
if mergeErr := viper.MergeConfigMap(local.AllSettings()); mergeErr != nil {
logging.Warn("Failed to merge local config", "error", mergeErr)
}
}
}

Expand Down
8 changes: 6 additions & 2 deletions internal/config/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ func MarkProjectInitialized() error {
if err != nil {
return fmt.Errorf("failed to create init flag file: %w", err)
}
defer file.Close()
defer func() {
if closeErr := file.Close(); closeErr != nil {
// Log the error but don't fail the function since the main operation succeeded
fmt.Fprintf(os.Stderr, "Warning: failed to close init flag file: %v\n", closeErr)
}
}()

return nil
}

4 changes: 3 additions & 1 deletion internal/db/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ func Connect() (*sql.DB, error) {

// Verify connection
if err = db.Ping(); err != nil {
db.Close()
if closeErr := db.Close(); closeErr != nil {
logging.Error("Failed to close database after ping failure", "error", closeErr)
}
return nil, fmt.Errorf("failed to connect to database: %w", err)
}

Expand Down
12 changes: 0 additions & 12 deletions internal/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,18 +563,6 @@ func createStyles(t theme.Theme) (removedLineStyle, addedLineStyle, contextLineS
// Rendering Functions
// -------------------------------------------------------------------------

func lipglossToHex(color lipgloss.Color) string {
r, g, b, a := color.RGBA()

// Scale uint32 values (0-65535) to uint8 (0-255).
r8 := uint8(r >> 8)
g8 := uint8(g >> 8)
b8 := uint8(b >> 8)
a8 := uint8(a >> 8)

return fmt.Sprintf("#%02x%02x%02x%02x", r8, g8, b8, a8)
}

// applyHighlighting applies intra-line highlighting to a piece of text
func applyHighlighting(content string, segments []Segment, segmentType LineType, highlightBg lipgloss.AdaptiveColor) string {
// Find all ANSI sequences in the content
Expand Down
10 changes: 5 additions & 5 deletions internal/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ func formatAsJSON(content string) string {
jsonBytes, err := json.MarshalIndent(response, "", " ")
if err != nil {
// In case of an error, return a manually formatted JSON
jsonEscaped := strings.Replace(content, "\\", "\\\\", -1)
jsonEscaped = strings.Replace(jsonEscaped, "\"", "\\\"", -1)
jsonEscaped = strings.Replace(jsonEscaped, "\n", "\\n", -1)
jsonEscaped = strings.Replace(jsonEscaped, "\r", "\\r", -1)
jsonEscaped = strings.Replace(jsonEscaped, "\t", "\\t", -1)
jsonEscaped := strings.ReplaceAll(content, "\\", "\\\\")
jsonEscaped = strings.ReplaceAll(jsonEscaped, "\"", "\\\"")
jsonEscaped = strings.ReplaceAll(jsonEscaped, "\n", "\\n")
jsonEscaped = strings.ReplaceAll(jsonEscaped, "\r", "\\r")
jsonEscaped = strings.ReplaceAll(jsonEscaped, "\t", "\\t")

return fmt.Sprintf("{\n \"response\": \"%s\"\n}", jsonEscaped)
}
Expand Down
5 changes: 4 additions & 1 deletion internal/history/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/google/uuid"
"github.com/opencode-ai/opencode/internal/db"
"github.com/opencode-ai/opencode/internal/logging"
"github.com/opencode-ai/opencode/internal/pubsub"
)

Expand Down Expand Up @@ -121,7 +122,9 @@ func (s *service) createWithVersion(ctx context.Context, sessionID, path, conten
})
if txErr != nil {
// Rollback the transaction
tx.Rollback()
if rollbackErr := tx.Rollback(); rollbackErr != nil {
logging.Warn("Failed to rollback transaction", "error", rollbackErr)
}

// Check if this is a uniqueness constraint violation
if strings.Contains(txErr.Error(), "UNIQUE constraint failed") {
Expand Down
4 changes: 3 additions & 1 deletion internal/llm/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,9 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
if err != nil {
if errors.Is(err, context.Canceled) {
agentMessage.AddFinish(message.FinishReasonCanceled)
a.messages.Update(context.Background(), agentMessage)
if updateErr := a.messages.Update(context.Background(), agentMessage); updateErr != nil {
logging.Warn("Failed to update agent message on cancellation", "error", updateErr)
}
return a.err(ErrRequestCancelled)
}
return a.err(fmt.Errorf("failed to process events: %w", err))
Expand Down
12 changes: 10 additions & 2 deletions internal/llm/agent/mcp-tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ func (b *mcpTool) Info() tools.ToolInfo {
}

func runTool(ctx context.Context, c MCPClient, toolName string, input string) (tools.ToolResponse, error) {
defer c.Close()
defer func() {
if closeErr := c.Close(); closeErr != nil {
logging.Warn("Failed to close MCP client", "error", closeErr)
}
}()
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Expand Down Expand Up @@ -158,7 +162,11 @@ func getTools(ctx context.Context, name string, m config.MCPServer, permissions
for _, t := range tools.Tools {
stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m))
}
defer c.Close()
defer func() {
if closeErr := c.Close(); closeErr != nil {
logging.Warn("Failed to close MCP client", "error", closeErr)
}
}()
return stdioTools
}

Expand Down
15 changes: 11 additions & 4 deletions internal/llm/models/local.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package models

import (
"cmp"
"encoding/json"
"net/http"
"net/url"
Expand Down Expand Up @@ -82,7 +81,11 @@ func listLocalModels(modelsEndpoint string) []localModel {
"endpoint", modelsEndpoint,
)
}
defer res.Body.Close()
defer func() {
if closeErr := res.Body.Close(); closeErr != nil {
logging.Debug("Failed to close response body", "error", closeErr)
}
}()

if res.StatusCode != http.StatusOK {
logging.Debug("Failed to list local models",
Expand Down Expand Up @@ -135,13 +138,17 @@ func loadLocalModels(models []localModel) {
}

func convertLocalModel(model localModel) Model {
contextWindow := model.LoadedContextLength
if contextWindow == 0 {
contextWindow = 4096
}
return Model{
ID: ModelID("local." + model.ID),
Name: friendlyModelName(model.ID),
Provider: ProviderLocal,
APIModel: model.ID,
ContextWindow: cmp.Or(model.LoadedContextLength, 4096),
DefaultMaxTokens: cmp.Or(model.LoadedContextLength, 4096),
ContextWindow: contextWindow,
DefaultMaxTokens: contextWindow,
CanReason: true,
SupportsAttachments: true,
}
Expand Down
6 changes: 4 additions & 2 deletions internal/llm/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func processContextPaths(workDir string, paths []string) string {
defer wg.Done()

if strings.HasSuffix(p, "/") {
filepath.WalkDir(filepath.Join(workDir, p), func(path string, d os.DirEntry, err error) error {
if walkErr := filepath.WalkDir(filepath.Join(workDir, p), func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
Expand All @@ -93,7 +93,9 @@ func processContextPaths(workDir string, paths []string) string {
}
}
return nil
})
}); walkErr != nil {
logging.Warn("Failed to walk directory", "error", walkErr, "path", p)
}
} else {
fullPath := filepath.Join(workDir, p)

Expand Down
9 changes: 5 additions & 4 deletions internal/llm/provider/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
cfg := config.Get()
if cfg.Debug {
// jsonData, _ := json.Marshal(preparedMessages)
// logging.Debug("Prepared messages", "messages", string(jsonData))
jsonData, _ := json.Marshal(preparedMessages)
logging.Debug("Prepared messages", "messages", string(jsonData))
}
attempts := 0
eventChan := make(chan ProviderEvent)
Expand All @@ -273,9 +273,10 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message

switch event := event.AsAny().(type) {
case anthropic.ContentBlockStartEvent:
if event.ContentBlock.Type == "text" {
switch event.ContentBlock.Type {
case "text":
eventChan <- ProviderEvent{Type: EventContentStart}
} else if event.ContentBlock.Type == "tool_use" {
case "tool_use":
currentToolCallID = event.ContentBlock.ID
eventChan <- ProviderEvent{
Type: EventToolUseStart,
Expand Down
1 change: 0 additions & 1 deletion internal/llm/provider/bedrock.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,3 @@ func (b *bedrockClient) stream(ctx context.Context, messages []message.Message,

return b.childProvider.stream(ctx, messages, tools)
}

Loading