Codex CLI Go Developer Guide: Auto-generate Tests, Fix go vet Errors & Go Workflow
Go is celebrated for its simplicity and performance, but large Go codebases still accumulate thin test coverage, lint backlogs, and tangled interface designs. Codex CLI understands Go idioms — table-driven tests, consumer-side interfaces, context.Context propagation — and can batch-generate tests, fix static analysis errors, and refactor concurrent code so you can focus on business logic. This guide covers the complete set of Go use cases.
1. AGENTS.md Configuration for Go Projects
Place an AGENTS.md file in the project root to tell Codex CLI about your Go project layout, allowed commands, and coding conventions. The following is a complete example for a standard Go service:
# Go Project Conventions
## Build & Test
- Always run `go build ./...` to verify compilation
- Run tests: `go test ./... -race -count=1`
- Lint: `golangci-lint run ./...`
- Format: `gofmt -l -w .` or `goimports -l -w .`
## Project Layout
- cmd/ contains executable entry points
- internal/ contains private packages (not exposed externally)
- pkg/ contains reusable public packages
- Follow the standard Go project layout
## Code Conventions
- Error handling: never discard errors with _
- Define interfaces at the consumer, not the implementor
- All exported functions must have godoc comments
- Use context.Context as the first parameter
What each section does:
- Build & Test: Tells Codex exactly which validation steps to run after each change. The
-raceflag enables data race detection;-count=1disables test caching. - Project Layout: Helps Codex place new files in the right directories. The
internal/boundary prevents incorrect cross-package imports. - Code Conventions: Core Go community idioms — interface isolation, godoc documentation, and context propagation — ensure Codex generates idiomatic Go rather than translated code from other languages.
golangci-lint to your allowed commands list is important — Codex can run lint immediately after each modification, creating a tight modify→validate feedback loop.
2. Auto-generating Table-Driven Tests
Table-driven tests are Go's canonical testing pattern, covering large numbers of edge cases in a compact structure. Codex CLI can analyze function signatures and business logic to generate complete test suites including normal inputs, boundary values, and error paths.
2.1 Basic prompts
$ codex "Generate table-driven tests for ParseUserID covering boundary conditions"
$ codex "Generate tests for all exported functions in the internal/auth package"
2.2 Example generated test
Below is the typical test file Codex produces for a ParseUserID function:
// Generated test example
func TestParseUserID(t *testing.T) {
tests := []struct {
name string
input string
want int64
wantErr bool
}{
{"valid id", "12345", 12345, false},
{"empty string", "", 0, true},
{"negative", "-1", 0, true},
{"overflow", "99999999999999999", 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseUserID(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParseUserID() error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.want {
t.Errorf("ParseUserID() = %v, want %v", got, tt.want)
}
})
}
}
2.3 Bulk test generation for an entire package
$ codex "Scan the internal/validator package and generate table-driven tests for every exported function:
- At least 4 test cases per function (happy path, zero value, boundary, error)
- Use t.Run to organize subtests
- Verify error messages contain expected keywords
- File naming: {package}_test.go"
2.4 Integration tests with mocks
$ codex "Generate tests for OrderService in internal/service/order.go:
- Use testify/mock to generate mocks for OrderRepository and PaymentGateway
- Test CreateOrder, CancelOrder, and GetOrderStatus methods
- Cover database error and payment timeout failure scenarios
- Assert mock call counts and arguments"
go test ./... -coverprofile=coverage.out && go tool cover -html=coverage.out to find uncovered branches, then ask Codex to add test cases for each uncovered path.
3. Fixing go vet and staticcheck Errors
One of the most efficient Codex CLI patterns is piping static analysis tool output directly to Codex for bulk fixes, keeping function behaviour unchanged.
3.1 Bulk fix workflow
$ golangci-lint run ./... 2>&1 | head -50
# Copy the output, then:
$ codex "Fix the following golangci-lint errors without changing function behaviour:
[paste lint output]"
Or pipe directly:
$ golangci-lint run ./... 2>&1 | codex "Fix all issues reported above. Keep function signatures unchanged. Do not introduce new lint errors."
3.2 Common go vet errors and how Codex fixes them
| Error type | Typical message | Codex fix strategy |
|---|---|---|
| Variable shadowing | declaration of "err" shadows declaration |
Rename the inner err, or reuse the outer variable with = |
| Unchecked error (errcheck) | Error return value of X not checked |
Add if err != nil handling, or use explicit _ with an explanatory comment |
| Unused parameter | unused parameter: ctx |
Replace with _ placeholder or actually use the parameter in the function body |
| Format string misuse | fmt.Errorf can be replaced by errors.New |
Replace no-arg fmt.Errorf calls with errors.New |
| Loop variable capture | loop variable v captured by func literal |
Copy before goroutine: v := v (required before Go 1.22) |
| Nilness check | impossible condition: non-nil pointer is nil |
Fix the condition logic or refactor into a clearer nil-check flow |
3.3 Linter-specific prompts
$ codex "Run go vet ./... and fix all reported issues"
$ codex "Fix all staticcheck SA-category errors (static analysis bugs). Do not change S-category suggestions (style)."
$ codex "Fix all errcheck warnings in internal/api/. For errors that genuinely don't need handling, add //nolint:errcheck with a brief explanation."
git commit) before a bulk lint fix so you can roll back if needed. After Codex applies fixes, run go test ./... -race to confirm behaviour is unchanged.
4. Interface Refactoring and Dependency Injection
Go's implicit interface satisfaction makes dependency injection elegant, but legacy code often depends directly on concrete types, making it hard to test. Codex CLI can automate the full interface extraction and dependency injection refactor.
4.1 Extract an interface from a concrete type
$ codex "Extract an interface from UserRepository and migrate to a dependency injection pattern"
4.2 Before: direct dependency on a concrete type
// Before: Service directly depends on the concrete database implementation
type UserService struct {
repo *PostgresUserRepository // concrete type — cannot be mocked
}
func NewUserService(db *sql.DB) *UserService {
return &UserService{repo: &PostgresUserRepository{db: db}}
}
4.3 After: program to an interface
// After: interface defined at the consumer (Service package)
type UserRepository interface {
FindByID(ctx context.Context, id int64) (*User, error)
Save(ctx context.Context, user *User) error
Delete(ctx context.Context, id int64) error
}
type UserService struct {
repo UserRepository // interface type — any implementation can be injected
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
4.4 Generate mocks for testing
$ codex "Generate a testify/mock implementation for the UserRepository interface (internal/mocks/user_repository.go) and update UserService tests to use it"
$ codex "Generate mockery-format mocks for all interfaces in the internal/service package, placing them in internal/mocks/"
5. Modernising Error Handling
Go 1.13 introduced %w wrapping and errors.Is/errors.As as the standard approach for error chains, but many codebases still use bare errors.New or fmt.Errorf without %w. Codex CLI can bulk-migrate error handling code.
5.1 Migrate to wrapped errors
$ codex "Migrate all fmt.Errorf calls in internal/service/ to use %w wrapping:
- fmt.Errorf(\"failed: \" + err.Error()) → fmt.Errorf(\"failed: %w\", err)
- errors.New(\"...\" + err.Error()) → fmt.Errorf(\"...: %w\", err)
Keep error message text unchanged — only change the format."
5.2 Add sentinel errors
$ codex "Add sentinel errors to internal/repository/errors.go:
- ErrNotFound: resource does not exist
- ErrAlreadyExists: resource already exists
- ErrUnauthorized: insufficient permission
- ErrInvalidInput: input validation failed
Return these from repository implementations and check them with errors.Is in the service layer."
5.3 Custom error types
$ codex "Create a ValidationError custom type that includes field name and reason, implements the error interface, and supports errors.As extraction for detailed information"
5.4 Audit existing error handling
$ codex "Audit error handling in cmd/api/main.go and find:
1. Silently discarded errors (_ assignment) that should be handled
2. Error messages with no context that need additional information
3. Critical errors that should use log.Fatal instead of fmt.Println
Propose fixes and implement them."
6. Concurrency Patterns (goroutine / channel)
Go's concurrency is its headline feature — but also the most common source of subtle bugs. Codex CLI can add context timeout controls, convert serial code to concurrent, and implement well-known concurrency patterns.
6.1 Add context timeout control
$ codex "Add context timeout control to this HTTP handler"
6.2 Serial to concurrent with a goroutine pool
$ codex "Rewrite this serial processing loop as a goroutine pool with a concurrency limit of 10"
Below is the semaphore-based goroutine pool Codex typically generates:
func ProcessItems(ctx context.Context, items []Item) error {
const maxConcurrency = 10
sem := make(chan struct{}, maxConcurrency)
var (
wg sync.WaitGroup
mu sync.Mutex
errs []error
)
for _, item := range items {
item := item // capture loop variable (required before Go 1.22)
wg.Add(1)
sem <- struct{}{}
go func() {
defer wg.Done()
defer func() { <-sem }()
if err := processItem(ctx, item); err != nil {
mu.Lock()
errs = append(errs, err)
mu.Unlock()
}
}()
}
wg.Wait()
return errors.Join(errs...)
}
6.3 More concurrency prompts
$ codex "Refactor FetchMultipleAPIs using errgroup with context cancellation — if any single API call fails, cancel the remaining requests"
$ codex "Add sync.RWMutex read-write locking to the LRU cache in cache.go to improve concurrent read performance"
$ codex "Audit goroutine usage in internal/worker/ and identify potential goroutine leaks (unclosed channels or missing context cancellation handling)"
go test ./... -race -count=3 (multiple runs increase the chance of detecting race conditions). Explicitly stating "must pass go test -race" in your prompt nudges Codex toward safer implementations.
7. Go Modules and Dependency Management
As projects grow, go.mod can accumulate stale dependencies, version conflicts, and packages with security vulnerabilities. Codex CLI can assist with dependency analysis and upgrades.
7.1 Common dependency management prompts
$ codex "Analyse go.mod dependencies, identify packages with known security vulnerabilities and upgrade them"
$ codex "Upgrade all direct dependencies in go.mod to their latest stable versions, update go.sum, and ensure go build ./... and go test ./... still pass"
$ codex "Identify unused indirect dependencies in go.mod, run go mod tidy, and explain changes to go.sum"
7.2 Resolving version conflicts
$ codex "Resolve the version conflict for google.golang.org/grpc in go.mod:
The project requires v1.58.0, but package A requires v1.60+ and package B requires v1.57.
Find a compatible version, update replace directives or upgrade the dependent packages."
7.3 Local replace directives
$ codex "Add a replace directive in go.mod pointing github.com/company/internal-lib to the local development path ../internal-lib, and explain how to remove this replacement in CI"
7.4 Go version upgrades
$ codex "Migrate the project from Go 1.21 to Go 1.22:
1. Update the go directive in go.mod
2. Simplify loops using the new for-range-integer syntax
3. Remove loop variable capture workarounds (fixed by default in 1.22)
4. Check for any breaking changes that affect current code"
govulncheck ./... (the official Go vulnerability scanner) with Codex: govulncheck ./... 2>&1 | codex "Fix the security vulnerabilities above by upgrading the affected dependencies."
8. GitHub Actions CI for Go
Delegate your Go CI configuration to Codex, or use the following battle-tested complete workflow covering build, test, lint, and coverage upload:
# .github/workflows/go-ci.yml
name: Go CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
- name: Build
run: go build ./...
- name: Test with Race Detector
run: go test ./... -race -coverprofile=coverage.out -covermode=atomic
- name: Vet
run: go vet ./...
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
- name: Upload Coverage
uses: codecov/codecov-action@v4
with:
files: ./coverage.out
The Codex prompt to generate this workflow from scratch:
$ codex "Generate a GitHub Actions CI workflow for this Go project, including tests, lint, and code coverage upload"
8.1 Multi-version matrix testing
$ codex "Extend the Go CI workflow to add a matrix: run tests against both Go 1.21 and 1.22, and across ubuntu, macos, and windows runners"
8.2 Codex AI code review in CI
$ codex "Add a Codex CLI code review step to the CI workflow:
- Trigger only on pull_request events
- Collect changed Go files from the PR
- Use Codex to check for: concurrency safety, error handling, interface design
- Post review findings as a PR comment"
go test -race and golangci-lint as required status checks in your branch protection rules. Enforcing these before merge prevents concurrency bugs and lint debt from accumulating.
9. FAQ
Does Codex understand Go's goroutine concurrency model?
Yes. Codex CLI understands goroutines, channels, sync.WaitGroup, sync.Mutex, sync/atomic, and the errgroup package. It can refactor serial code into concurrent versions, add context timeout handling, and flag data races that the -race detector would catch. Specifying your project's concurrency strategy in AGENTS.md (maximum goroutines, use of errgroup vs raw WaitGroup, etc.) leads to more accurate implementations.
How do I get Codex to generate idiomatic Go code?
Set conventions in AGENTS.md: wrap errors with fmt.Errorf("context: %w", err), define interfaces at the consumer not the implementor, require godoc comments on all exported functions, and use context.Context as the first parameter. Add gofmt and golangci-lint to your allowed commands list so Codex automatically validates formatting after every change.
How well does Codex handle Go generics?
Codex CLI fully supports Go 1.18+ generics syntax. It can generate generic functions and data structures with interface constraints, understands built-in constraints like comparable and any, and can retrofit existing code with generics to remove repetition. Mention your Go version explicitly in the prompt (e.g., "Go 1.22, use generics") for the most precise output.
Can Codex help me migrate from Go 1.18 to 1.22?
Yes. Tell Codex your source and target versions and it will find upgrade opportunities: replacing sort.Slice with slices.SortFunc, simplifying map operations with the maps package, adopting the 1.22 for-range-integer syntax, and removing loop variable capture workarounds (fixed by default in 1.22). Update the go directive in go.mod first, then run: "Migrate this project from Go 1.18 to 1.22, use new features to simplify code, list all changes."