# Go Lint

> Linting and formatting Go code with `gofmt`, `goimports`, `golangci-lint`, and a curated `.golangci.yml`. ALWAYS use this skill when setting up or troubleshooting Go linting — installing golangci-lint, dropping the bundled `.golangci.yml` into a project, configuring pre-commit hooks, picking which linters to enable (errcheck, govet, staticcheck, unused, misspell, prealloc, gosec, revive, gocritic), suppressing findings with `//nolint` comments, reading lint output, or wiring lint into CI. Pair with go-style for the underlying idioms most lint rules enforce.

- Skill: `marsolab/go-lint` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add marsolab/go-lint`
- Raw SKILL.md: https://api.skillmd.com/api/skills/marsolab/go-lint/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: marsolab (https://skillmd.com/u/marsolab)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/marsolab/go-lint

---


# Go Lint

Run `gofmt`, `goimports`, and `golangci-lint` on every commit. The
formatters fix style automatically; the linter catches the bugs the
compiler doesn't.

For the comprehensive reference, see `references/linting.md`. The
bundled config lives at `assets/golangci.yml` and the setup script at
`scripts/setup_golangci_lint.sh`.

## Setup script

Drop the bundled config into a project and (optionally) install a
pre-commit hook:

```bash
scripts/setup_golangci_lint.sh /path/to/project
```

The script copies `.golangci.yml` and prompts before installing the
hook so you can opt out.

## Day-to-day commands

Run the portable helper from the target Go project's root when available:

```bash
/path/to/go-lint/scripts/run_lint.sh
```

Or run the underlying commands directly:

```bash
# Run every enabled linter
golangci-lint run ./...

# Auto-fix what's fixable
golangci-lint run --fix ./...

# Lint a subset
golangci-lint run ./internal/...

# Show which linters are active
golangci-lint linters

# Format Go code (always before commit)
gofmt -w .
goimports -w .          # sorts and removes unused imports
```

## Required tools

- **gofmt** / **goimports**: non-negotiable. `goimports` is a strict
  superset of `gofmt`.
- **go vet**: built into the toolchain; catches misuse of stdlib types.
- **golangci-lint**: the meta-linter; runs many analyzers in parallel
  with shared parsing.

## Linters worth enabling

The bundled `.golangci.yml` turns these on:

| Linter | What it catches |
|---|---|
| `errcheck` | Unchecked errors |
| `govet` | Stdlib misuse, shadowing, struct tag typos |
| `staticcheck` | The biggest set of static-analysis rules |
| `unused` | Dead code |
| `misspell` | Typos in comments and strings |
| `prealloc` | Slices that should be preallocated |
| `gosec` | Common security issues |
| `revive` | Successor to golint; readable rules |
| `gocritic` | Many opinionated readability checks |
| `bodyclose` | Unclosed `http.Response.Body` |
| `nilerr` | Returning a non-nil err as nil |
| `errorlint` | `%w` and wrapping mistakes |

## Suppressing findings

Prefer fixing over suppressing. When you must suppress, do so narrowly
and explain why:

```go
//nolint:gosec // not user input — read from a generated config file
buf, err := os.ReadFile(path)
```

`//nolint` without a linter name disables every linter on that line —
avoid it. Always name the linter and add a comment.

`//nolint:errcheck` is almost never legitimate. A flagged unchecked error
— usually a deferred `Close`, `Flush`, or `Write` — is an error to
handle, not noise to silence. The bundled config enables `check-blank`,
so `_ = f.Close()` is reported, and it never applies golangci-lint's
`std-error-handling` exclusion preset, so a bare `f.Close()` is flagged
too. Handle them with a named return and a deferred `errors.Join` (see
the go-errors skill) instead of suppressing.

## Pre-commit integration

The setup script writes a Git hook that runs `golangci-lint run` on
staged Go files. To skip in an emergency, use a regular `git commit`
after fixing — never `--no-verify`. If the hook is wrong, fix the rule.

## CI integration

```yaml
# .github/workflows/ci.yml fragment
- uses: golangci/golangci-lint-action@v6
  with:
    version: v1.61.0
    args: --timeout=5m
```

Pin the version. Floating `latest` will surprise you with new findings
when the linter releases.

## When to load a sibling skill

| Task | Skill |
|---|---|
| Why a rule fires (the underlying idiom) | go-style |
| Catching shadowed `ctx` from `:=` | go-style (pitfalls) |
| Tests that should run alongside lint | go-testing |
| HTTP handlers that frequently trigger `bodyclose` | go-http |

