agentsclimarketplace

Debug go

Skill yerdaulet-damir/vibe-coding-rules/.claude/skills/debug-go

Systematic 5-step debugging flow for Go 1.22+ services. Load when a test fails, a goroutine leaks, a downstream provider hangs, errors lose context, or production logs are unhelpful. Forces layer isolation (handler vs service vs repo vs provider) and runs the 5 most common Go antipattern greps before any code change — prevents the "add a goroutine to fix a slow handler" cascade.From its SKILL.md

Install
npx -y skills add yerdaulet-damir/vibe-coding-rules --skill debug-go

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 8 commands, including `go test -run TestService_RaceOnConcurrentCharge -race -v ./internal/credits/` and 7 more.

SKILL.md

7.0 KB, ~1.8k tokens by cl100k_base, as published. Nobody here has run it

debug-go

Stop. Do not edit any file yet. Work through these 5 steps in order.


Step 1 — Locate the layer

Go bugs almost always live in one of these layers. Identify which one before touching code.

SymptomLikely layerFirst grep
500 in prod, no useful logHandler — error mappinggrep -n "errors.Is|errors.As" internal/server/handlers.go
Wrong amount / state after mutationService / repogrep -n "Hold|Confirm|Refund" internal/<domain>/service.go
Hangs under loadHTTP client / bulkheadgrep -rn "http.DefaultClient|MaxConnsPerHost" internal/
context deadline exceeded everywhereMissing ctx propagationgrep -rn "context.Background()|context.TODO()" internal/
Goroutine leak in pproferrgroup not used / unclosed channelsgrep -rn "go func\b" internal/
Test passes locally, race on CIShared mutable statego test ./... -race
Panic crashes serverMissing recover middlewaregrep -rn "recover()" internal/server/
Downstream API change broke usProvider parsergrep -rn "json.Unmarshal|json.NewDecoder" internal/providers/

Pick one layer. Do not touch any other layer in this debug pass.


Step 2 — Run the 5 most common Go antipattern greps

# Antipattern 1: http.DefaultClient (no per-provider isolation)
grep -rn "http\.DefaultClient" internal/

# Antipattern 2: context.Background() / context.TODO() in non-main code
grep -rn "context\.Background()\|context\.TODO()" internal/ \
  --include="*.go" | grep -v "_test.go"

# Antipattern 3: panic in production code
grep -rn "panic(" internal/ --include="*.go" | grep -v "_test.go\|recover"

# Antipattern 4: fmt.Println / log.Printf instead of slog
grep -rn "fmt\.Println\|log\.Printf" internal/ --include="*.go" | grep -v "_test.go"

# Antipattern 5: errors not wrapped with %w
grep -rn "errors\.New(\"[^%]*: \"" internal/ --include="*.go"
# Hits with concatenation suggest a missed `%w` opportunity.
ResultRoot causePrinciple
http.DefaultClient usedBulkhead broken — one slow downstream blocks allF4
context.Background() in handler chainLost cancellation/deadline propagationF2
panic() in handler/serviceServer crashes on edge caseF3
fmt.Println/log.PrintfLogs without context, unsearchableF6
errors.New with concatenated contextLost error chain, errors.Is/As failsF3

Step 3 — Reproduce with go test -run and -race

For service/repo bugs — write a failing test first:

func TestService_RaceOnConcurrentCharge(t *testing.T) {
    t.Parallel()
    svc := newTestService(t, dec("10.00"))
    ctx := context.Background()

    // Two concurrent charges with the same idempotency key.
    // Should hold once, not twice.
    var wg sync.WaitGroup
    var ids [2]string
    for i := 0; i < 2; i++ {
        i := i
        wg.Add(1)
        go func() {
            defer wg.Done()
            id, _ := svc.Charge(ctx, "u-1", dec("4.00"), "idem-x")
            ids[i] = id
        }()
    }
    wg.Wait()
    if ids[0] != ids[1] {
        t.Fatalf("idempotency violated: %s vs %s", ids[0], ids[1])
    }
}

Run it:

go test -run TestService_RaceOnConcurrentCharge -race -v ./internal/credits/

It must FAIL before the fix.

For HTTP client / provider bugs — use httptest.Server to simulate the failure:

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusTooManyRequests)
}))
defer srv.Close()

p := &FalAI{client: srv.Client(), baseURL: srv.URL, log: slog.Default()}
_, err := p.Generate(context.Background(), JobRequest{ModelID: "m", Prompt: "p"})
if !errors.Is(err, ErrRateLimited) {
    t.Fatalf("expected ErrRateLimited, got %v", err)
}

Step 4 — Fix in the correct layer

Minimum change. Do not refactor unrelated code.

LayerWhere to fix
Handler / error → HTTP codeinternal/server/handlers.go
Business logicinternal/<domain>/service.go
Persistence / concurrencyinternal/<domain>/repository.go
External API parsinginternal/providers/<name>.go (the ACL)
HTTP client configinternal/httpclient/client.go
Logging contextinternal/context/keys.go + middleware
Graceful shutdowninternal/server/run.go

Goroutine leaks specifically: add errgroup with a parent context.WithCancel. Every go func should be replaceable by g.Go(func() error { ... }). (Principle F8.)


Step 5 — Verify

# Type / vet check
go vet ./...

# Race detector — catches concurrency bugs you can't see by reading
go test ./... -race

# All tests pass
go test ./...

# (If you have a linter)
golangci-lint run

Then re-run all 5 antipattern greps from Step 2. You must not have introduced any new violations while fixing.


Common error → root cause table

Error / SymptomWhere to lookLikely cause
runtime error: invalid memory addressnil pointerA struct field not initialized — check the constructor
concurrent map read and writeshared mutable mapAdd sync.Mutex or use sync.Map if read-heavy
context deadline exceeded cascading everywhereparent ctxProbably a fixed context.WithTimeout(ctx, 1s) upstream
dial tcp: i/o timeoutnetwork or DNSCheck provider client Timeout; check pod DNS
too many open filesFD exhaustionBulkhead violation (F4): a hung downstream eating the pool
Test hangs foreverUnbuffered channelProducer goroutine exited without sending; receiver waits forever
interface conversion: ... is not ...wrong concrete typeHandler accepts a too-wide interface; tighten with the smallest method set
Panic mid-request, server stays up but log is silentrecover swallowsMake sure the recover middleware logs rec and debug.Stack()
Server doesn't drain on SIGTERMsignal.Notify not wiredUse signal.NotifyContext (Go 1.16+) — Principle F7
Provider response field appears null after parseUnmarshal hit zero valueCheck JSON tag spelling; use *string if true-null matters

Verification

The skill was applied correctly when:

  • A reproducible test exists (Step 3) — failing before, passing after
  • Fix touches exactly one layer
  • All 5 antipattern greps still come up clean
  • go vet ./... && go test ./... -race exits 0
  • No new file exceeds 500 LOC; no new package named utils/helpers/common

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.