go

Seeded Go defects — ignored errors, leaked goroutines, missing context propagation.

What this is

20 deliberate defects, each carrying a marker naming the rule in standards/ it violates. It measures recall, and its pass condition is that every one of the 12 BLOCKER markers is flagged. The rule id in each marker means the scorer grades attribution too: a reviewer that finds the bug but cites the wrong rule counts as caught, and separately as misattributed.

How to onboard it

Nothing is installed and nothing here is ever merged. The corpus is used by opening a throwaway pull request on a pilot repository already onboarded for this stack, adding this directory and seeded/clean together. Label it redline-exempt so the readiness gate does not block a pull request nobody will merge, wait for the automated review to finish, then score it.

seed-canary.yml does exactly this on a schedule and closes the pull request afterwards, including when the run fails.

How to use it

terminal
$ GH_TOKEN=... npx redlinegate metrics score-seeds --repo <org>/<repo> --pr <n>
SeedSeverityRule it violatesDefect
1BLOCKERgo/error-laundered-to-nilchecked error returned as success
1BLOCKERcore/hardcoded-secretshardcoded credential
2BLOCKERgo/typed-nil-in-interfacenil *ValidationError returned as a non-nil error
2BLOCKERgo/ignored-errorserror ignored
3BLOCKERgo/sql-rows-lifecyclerows never closed and rows.Err never checked
3BLOCKERcore/query-string-concatenationSQL built by concatenation with request input
4BLOCKERgo/nil-check-after-derefdereferenced before its nil check
4BLOCKERgo/missing-ctx-propagationcontext.Background() inside request handling drops cancellation
6BLOCKERmicroservices/swallowed-errorserror logged then treated as success
7BLOCKERgo/goroutine-leaksgoroutine blocks forever on an unbuffered channel nobody reads
8BLOCKERgo/writing-nil-mapwriting to a nil map panics at runtime
9BLOCKERcore/type-checker-suppressionlinter suppression with no explanation and no ticket
5HIGHgo/missing-client-server-timeoutpackage-level server has no timeouts
5HIGHgo/missing-client-server-timeouthttp.Client with no timeout — the zero value is infinite
6HIGHgo/nil-nil-returnmissing user returned as nil, nil
7HIGHgo/error-wrap-verb%v breaks errors.Is upstream
8HIGHgo/fatal-in-test-goroutinet.Fatal from a goroutine the test started
9HIGHgo/context-grown-in-loopcontext chained on every iteration
10HIGHgo/untagged-wire-structmarshalled struct with no field tags
10HIGHcore/untracked-todoplaceholder with no ticket reference

Expected output

A score from scripts/score-seeds.mjs: BLOCKER recall, false positives, and rule attribution. The canary appends it to data/seed-scores.jsonl and fails the run if BLOCKER recall drops below 1.0 or the clean corpus attracts a false positive. Of the 20 defects here, 12 BLOCKER and 8 HIGH — only the BLOCKER count is a pass condition.

How to edit it

Add a defect by adding the code and one marker line above it. There is no separate expectations file to keep in sync — scripts/score-seeds.mjs parses the markers straight out of the source:

marker format
SEED <n> [BLOCKER|HIGH|SUGGESTION] (<stack>/<rule-slug>) <short description>

scripts/validate.mjs fails CI if a seed cites a rule that does not exist, or claims a severity higher than that rule carries in the standard — so a marker cannot quietly drift away from the rule it is testing.

The full file

seeded/go/anti_slop.go · 85 lines · 2.2 KB
// DO NOT MERGE — Redline validation seed (anti-slop rules).
package seeded

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"testing"
)

func SaveUser(repo Repo, u User) error {
	if err := repo.Insert(u); err != nil {
		// SEED 1 [BLOCKER] (go/error-laundered-to-nil) checked error returned as success
		return nil
	}
	return nil
}

func validate(name string) error {
	var verr *ValidationError
	if name == "" {
		verr = &ValidationError{Field: "name"}
	}
	// SEED 2 [BLOCKER] (go/typed-nil-in-interface) nil *ValidationError returned as a non-nil error
	return verr
}

func orderIDs(ctx context.Context, db *sql.DB) ([]int, error) {
	// SEED 3 [BLOCKER] (go/sql-rows-lifecycle) rows never closed and rows.Err never checked
	rows, err := db.QueryContext(ctx, "SELECT id FROM orders")
	if err != nil {
		return nil, err
	}
	var ids []int
	for rows.Next() {
		var id int
		_ = rows.Scan(&id)
		ids = append(ids, id)
	}
	return ids, nil
}

func start(cfg *Config) error {
	// SEED 4 [BLOCKER] (go/nil-check-after-deref) dereferenced before its nil check
	addr := fmt.Sprintf(":%d", cfg.Port)
	if cfg == nil {
		return fmt.Errorf("nil config")
	}
	// SEED 5 [HIGH] (go/missing-client-server-timeout) package-level server has no timeouts
	return http.ListenAndServe(addr, nil)
}

func (r *Repo) Find(id string) (*User, error) {
	// SEED 6 [HIGH] (go/nil-nil-return) missing user returned as nil, nil
	return nil, nil
}

func parseConfig(b []byte, cfg *Config) error {
	if err := json.Unmarshal(b, cfg); err != nil {
		// SEED 7 [HIGH] (go/error-wrap-verb) %v breaks errors.Is upstream
		return fmt.Errorf("parse config: %v", err)
	}
	return nil
}

func TestWorker(t *testing.T) {
	go func() {
		// SEED 8 [HIGH] (go/fatal-in-test-goroutine) t.Fatal from a goroutine the test started
		t.Fatal("worker failed")
	}()
}

func process(ctx context.Context, items []Item) {
	for _, item := range items {
		// SEED 9 [HIGH] (go/context-grown-in-loop) context chained on every iteration
		ctx = context.WithValue(ctx, itemKey, item.ID)
		handle(ctx, item)
	}
}

// SEED 10 [HIGH] (go/untagged-wire-struct) marshalled struct with no field tags
type Event struct{ UserID string; Amount int }
seeded/go/seeded_violations.go · 61 lines · 2.0 KB
// DO NOT MERGE — Redline validation seed.
// Every `SEED n [SEVERITY] (rule-id)` marker must be flagged at that severity or higher,
// citing that rule id. Score with scripts/score-seeds.mjs.
package seed

import (
	"context"
	"database/sql"
	"fmt"
	"net/http"
)

// SEED 1 [BLOCKER] (core/hardcoded-secrets) hardcoded credential
const dbPassword = "Pr0d-Sup3r-S3cret-2026!"

func Handler(w http.ResponseWriter, r *http.Request, db *sql.DB) {
	// SEED 2 [BLOCKER] (go/ignored-errors) error ignored
	// SEED 3 [BLOCKER] (core/query-string-concatenation) SQL built by concatenation with request input
	rows, _ := db.Query("SELECT id FROM users WHERE name = '" + r.URL.Query().Get("name") + "'")
	defer rows.Close()

	// SEED 4 [BLOCKER] (go/missing-ctx-propagation) context.Background() inside request handling drops cancellation
	go process(context.Background())

	// SEED 5 [HIGH] (go/missing-client-server-timeout) http.Client with no timeout — the zero value is infinite
	client := &http.Client{}
	resp, err := client.Get("https://internal-api/balance")
	if err != nil {
		// SEED 6 [BLOCKER] (microservices/swallowed-errors) error logged then treated as success
		fmt.Println(err)
	}
	_ = resp
	w.WriteHeader(http.StatusOK)
}

func process(ctx context.Context) {
	// SEED 7 [BLOCKER] (go/goroutine-leaks) goroutine blocks forever on an unbuffered channel nobody reads
	ch := make(chan int)
	go func() {
		ch <- 1
	}()
}

// SEED 8 [BLOCKER] (go/writing-nil-map) writing to a nil map panics at runtime
func Tally(values []string) map[string]int {
	var counts map[string]int
	for _, v := range values {
		counts[v]++
	}
	return counts
}

// SEED 9 [BLOCKER] (core/type-checker-suppression) linter suppression with no explanation and no ticket
func writeAll(w http.ResponseWriter, b []byte) {
	w.Write(b) //nolint:errcheck
}

// SEED 10 [HIGH] (core/untracked-todo) placeholder with no ticket reference
// TODO: fall back to the secondary region when the primary is draining
func failover() {}