agentsclimarketplace

Go standard library

Skill v0lka/skills/development/idiomatic-go/go-standard-library

A set of AI agent skills for research and development tasks.

Install
npx -y skills add v0lka/skills --skill go-standard-library

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

One thing to look at

  • 13 stars13 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.

What its author says it does

Copied from the file, not written here

Guides the agent to avoid common Go standard library mistakes when writing or reviewing Go code. Covers time.Duration misuse, time.After memory leaks, JSON handling pitfalls (type embedding, monotonic clock, map of any), SQL mistakes (sql.Open, connection pooling, prepared statements, null values, row iteration errors), closing transient resources (HTTP body, sql.Rows, os.File), missing return after HTTP error replies, and using default HTTP client/server without timeouts. Use when writing, reviewing, or refactoring code that uses the time, encoding/json, database/sql, net/http, or os packages.

SKILL.md

12.7 KB, as published. Nobody here has run it

Go Standard Library

Rules and patterns for correct use of Go's standard library based on common mistakes #75-#81.


1. Time Duration (#75)

Rule: Never pass a bare integer to functions accepting time.Duration. Always use the time API constants.

time.Duration is nanoseconds as int64. Passing 1000 means 1000 nanoseconds (1 microsecond), not 1 second.

// BAD: 1000 nanoseconds, not 1 second
ticker := time.NewTicker(1000)

// GOOD: explicit duration units
ticker := time.NewTicker(time.Second)
ticker := time.NewTicker(1000 * time.Millisecond)

This applies to all functions/methods accepting time.Duration: time.NewTicker, time.NewTimer, time.Sleep, time.After, context.WithTimeout, etc.


2. time.After Memory Leaks (#76)

Rule: Never use time.After inside a loop or any repeatedly-called function (HTTP handler, Kafka consumer, etc.). Use time.NewTimer with Reset instead.

Each time.After call allocates ~200 bytes that cannot be freed until the timer expires. In a hot loop, this causes unbounded memory growth.

// BAD: leaks memory -- each iteration allocates a timer that lives for 1 hour
func consumer(ch <-chan Event) {
    for {
        select {
        case event := <-ch:
            handle(event)
        case <-time.After(time.Hour):
            log.Println("warning: no messages received")
        }
    }
}

// GOOD: reuse a single timer
func consumer(ch <-chan Event) {
    timerDuration := time.Hour
    timer := time.NewTimer(timerDuration)
    defer timer.Stop()
    for {
        timer.Reset(timerDuration)
        select {
        case event := <-ch:
            handle(event)
        case <-timer.C:
            log.Println("warning: no messages received")
        }
    }
}

time.After is fine for one-shot use outside loops.


3. JSON Handling (#77)

3.1 Type Embedding Hijacks Marshaling

Rule: If an embedded type implements json.Marshaler or json.Unmarshaler, the parent struct silently inherits that behavior, overriding the default field-by-field marshaling.

time.Time implements json.Marshaler. Embedding it causes the entire struct to marshal as just the time value, dropping all other fields.

// BAD: marshals as "2021-05-18T21:15:08Z" -- ID is silently lost
type Event struct {
    ID int
    time.Time
}

// GOOD: use a named field
type Event struct {
    ID   int
    Time time.Time
}

When reviewing: flag any embedded type that implements json.Marshaler/json.Unmarshaler (e.g., time.Time, encoding.TextMarshaler implementors). Prefer named fields unless the embedding is intentional and a custom MarshalJSON is defined on the parent.

3.2 Monotonic Clock Breaks Equality

Rule: time.Now() includes a monotonic clock reading. JSON marshal/unmarshal strips it. Comparing the original and unmarshaled structs with == returns false.

t := time.Now()
event1 := Event{Time: t}
b, _ := json.Marshal(event1)
var event2 Event
_ = json.Unmarshal(b, &event2)

event1 == event2 // false -- monotonic component differs

Fixes:

  • Compare time fields with time.Time.Equal() (ignores monotonic component).
  • Strip monotonic before storing: t.Truncate(0) or t.Round(0).
  • For tests, use t.UTC().Truncate(0) to normalize both timezone and monotonic.

3.3 Map of any: Numerics Are float64

Rule: When unmarshaling JSON into map[string]any, all numeric values become float64 -- even integers. Incorrect type assertions (e.g., to int) cause panics.

var m map[string]any
json.Unmarshal([]byte(`{"id": 32}`), &m)
// m["id"] is float64(32), NOT int

// BAD: panics at runtime
id := m["id"].(int)

// GOOD: assert to float64, then convert
id := int(m["id"].(float64))

Prefer unmarshaling into a concrete struct when the schema is known.


4. SQL Mistakes (#78)

4.1 sql.Open Does Not Connect

Rule: sql.Open may only validate arguments without establishing a connection. Always follow it with db.Ping() or db.PingContext() to verify the database is reachable.

db, err := sql.Open("mysql", dsn)
if err != nil {
    return err
}
if err := db.PingContext(ctx); err != nil {
    return err
}

4.2 Connection Pool Configuration

Rule: *sql.DB is a connection pool, not a single connection. For production, always configure these parameters:

MethodDefaultGuidance
SetMaxOpenConnsUnlimitedMust set. Match what the DB can handle.
SetMaxIdleConns2Increase for high-concurrency workloads to avoid frequent reconnects.
SetConnMaxIdleTimeUnlimitedSet to release connections after traffic bursts.
SetConnMaxLifetimeUnlimitedSet when connecting through a load balancer.
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxIdleTime(5 * time.Minute)
db.SetConnMaxLifetime(1 * time.Hour)

4.3 Use Prepared Statements

Rule: Use db.Prepare / db.PrepareContext for repeated queries and any query built from untrusted input (SQL injection defense).

stmt, err := db.PrepareContext(ctx, "SELECT * FROM orders WHERE id = ?")
if err != nil {
    return err
}
defer stmt.Close()

rows, err := stmt.QueryContext(ctx, id)

4.4 Null Values

Rule: Scanning a SQL NULL into a non-pointer Go type fails at runtime. Use a pointer or sql.NullXXX type for nullable columns.

// Option A: pointer
var department *string
err := rows.Scan(&department, &age) // department is nil if NULL

// Option B: sql.NullString (preferred -- clearer intent)
var department sql.NullString
err := rows.Scan(&department, &age)
if department.Valid {
    fmt.Println(department.String)
}

Available null types: sql.NullString, sql.NullBool, sql.NullInt32, sql.NullInt64, sql.NullFloat64, sql.NullTime.

4.5 Check rows.Err After Iteration

Rule: rows.Next() can return false either because all rows were consumed OR because an error occurred preparing the next row. Always check rows.Err() after the loop.

for rows.Next() {
    if err := rows.Scan(&department, &age); err != nil {
        return err
    }
}
if err := rows.Err(); err != nil {
    return err
}

5. Closing Transient Resources (#79)

Rule: Any struct implementing io.Closer must be closed. Use defer immediately after the creation/error-check.

5.1 HTTP Response Body

  • Always close resp.Body when err == nil, even if you don't read it.
  • Closing without reading may prevent TCP connection reuse. To preserve keep-alive, drain the body first.
resp, err := client.Get(url)
if err != nil {
    return err
}
defer resp.Body.Close()

// If you don't need the body but want keep-alive:
_, _ = io.Copy(io.Discard, resp.Body)

Note: On the server side, the request body is closed automatically. No action needed.

5.2 sql.Rows

Forgetting to close sql.Rows leaks a database connection back to the pool.

rows, err := db.QueryContext(ctx, query)
if err != nil {
    return err
}
defer rows.Close()

5.3 os.File

Not closing os.File delays release until GC. For writable files, the close error may surface a buffered write failure -- propagate it.

// Read-only: log close errors
f, err := os.Open(filename)
if err != nil {
    return err
}
defer func() {
    if err := f.Close(); err != nil {
        log.Printf("failed to close file: %v", err)
    }
}()

// Writable: propagate close errors via named return
func writeToFile(filename string, content []byte) (err error) {
    f, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer func() {
        closeErr := f.Close()
        if err == nil {
            err = closeErr
        }
    }()
    _, err = f.Write(content)
    return
}

If durability matters, call f.Sync() before close. Then close errors can be safely ignored.


6. Return After HTTP Error Reply (#80)

Rule: http.Error does NOT stop handler execution. Always return immediately after it.

// BAD: execution continues after error reply
func handler(w http.ResponseWriter, req *http.Request) {
    err := foo(req)
    if err != nil {
        http.Error(w, "foo", http.StatusInternalServerError)
    }
    // This runs even on error -- may panic on nil pointer, or write duplicate response
    result := bar(req)
    w.Write(result)
}

// GOOD: return after error reply
func handler(w http.ResponseWriter, req *http.Request) {
    err := foo(req)
    if err != nil {
        http.Error(w, "foo", http.StatusInternalServerError)
        return
    }
    result := bar(req)
    w.Write(result)
}

This applies to any function that writes an HTTP error: http.Error, w.WriteHeader, json.NewEncoder(w).Encode, etc. Missing return causes duplicate writes and a superfluous WriteHeader warning.


7. Default HTTP Client and Server (#81)

7.1 HTTP Client

Rule: Never use http.DefaultClient, http.Get(), http.Post(), or &http.Client{} in production. They have no timeouts -- requests can hang forever.

// GOOD: production HTTP client
client := &http.Client{
    Timeout: 5 * time.Second,
    Transport: &http.Transport{
        DialContext: (&net.Dialer{
            Timeout: time.Second,
        }).DialContext,
        TLSHandshakeTimeout:   time.Second,
        ResponseHeaderTimeout:  time.Second,
        MaxIdleConnsPerHost:    100,
        IdleConnTimeout:        90 * time.Second,
    },
}

Key timeouts:

TimeoutCovers
net.Dialer.TimeoutTCP dial
Transport.TLSHandshakeTimeoutTLS handshake
Transport.ResponseHeaderTimeoutWaiting for response headers
Client.TimeoutEntire request (dial through body read)

Connection pooling: Default MaxIdleConnsPerHost is 2. For high-throughput to a single host, increase it to avoid constant reconnection overhead.

7.2 HTTP Server

Rule: Never use &http.Server{}, http.ListenAndServe(), or http.Serve() without timeouts in production. Clients can exploit missing timeouts to exhaust resources.

// GOOD: production HTTP server
srv := &http.Server{
    Addr:              ":8080",
    ReadHeaderTimeout: 500 * time.Millisecond,
    ReadTimeout:       time.Second,
    IdleTimeout:       time.Second,
    Handler:           http.TimeoutHandler(handler, 5*time.Second, "request timeout"),
}

Key timeouts:

TimeoutCovers
ReadHeaderTimeoutReading request headers. Must set to prevent Slowloris attacks.
ReadTimeoutReading entire request (headers + body).
http.TimeoutHandlerHandler execution time. Returns 503 on timeout and cancels the handler's context.
IdleTimeoutKeep-alive idle time. Falls back to ReadTimeout if unset.

Prefer http.TimeoutHandler over the older WriteTimeout field: it returns a proper 503 status, cancels the handler context, and has consistent behavior regardless of TLS.


Quick Reference

MistakeRule
#75 Time durationNever pass bare integers; use time.Second, time.Millisecond, etc.
#76 time.After leakUse time.NewTimer + Reset in loops/repeated code paths
#77a Embedded JSONEmbedded types can hijack marshal/unmarshal; prefer named fields
#77b Monotonic clockUse Equal() or Truncate(0) for time comparison after JSON round-trip
#77c Map of anyAll JSON numbers become float64; assert correctly or use a struct
#78a sql.OpenFollow with Ping/PingContext to verify connectivity
#78b Connection poolSet MaxOpenConns, MaxIdleConns, ConnMaxIdleTime, ConnMaxLifetime
#78c Prepared stmtsUse Prepare for repeated queries and untrusted input
#78d Null valuesUse *T or sql.NullXXX for nullable columns
#78e Row iterationAlways check rows.Err() after the rows.Next() loop
#79 Close resourcesdefer Close() for resp.Body, sql.Rows, os.File, and all io.Closer
#80 Return after errorhttp.Error does not stop execution; always return after it
#81a HTTP clientSet dial, TLS, header, and overall timeouts; tune MaxIdleConnsPerHost
#81b HTTP serverSet ReadHeaderTimeout; use http.TimeoutHandler; set IdleTimeout

Keep looking

Skills are one crate of 328,083. 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.