golangci/golangci-lint · error

failed to gob decode: %w

Error message

failed to gob decode: %w

What it means

internal/cache.Cache.decode wraps any error returned by gob decoding of cached bytes into "failed to gob decode: %w". It is thrown when the stored byte buffer cannot be decoded into the requested value, and it is instrumented via sw.TrackStageErr under the "gob" stage. The caller (Cache.Get) surfaces this when a cache entry exists but is unreadable.

Source

Thrown at internal/cache/cache.go:287

func (c *Cache) encode(data any) (*bytes.Buffer, error) {
	buf := &bytes.Buffer{}
	err := c.sw.TrackStageErr("gob", func() error {
		return gob.NewEncoder(buf).Encode(data)
	})
	if err != nil {
		return nil, fmt.Errorf("failed to gob encode: %w", err)
	}

	return buf, nil
}

func (c *Cache) decode(b []byte, data any) error {
	err := c.sw.TrackStageErr("gob", func() error {
		return gob.NewDecoder(bytes.NewReader(b)).Decode(data)
	})
	if err != nil {
		return fmt.Errorf("failed to gob decode: %w", err)
	}

	return nil
}

func SetSalt(b *bytes.Buffer) {
	cache.SetSalt(b.Bytes())
}

func DefaultDir() (string, error) {
	cacheDir, _, err := cache.DefaultDir()
	return cacheDir, err
}

View on GitHub (pinned to ed7a235d2d)

Solutions

  1. Delete the stale/corrupt cache directory (e.g. the app's cache command or manually remove the cache dir) so entries are regenerated
  2. Ensure the type passed to Decode matches the type that was gob-encoded; gob is type-strict about struct names and exported fields
  3. Bump a cache format/version key so old entries written by incompatible versions are ignored instead of decoded
  4. Check the wrapped error (%w) for the exact gob failure: 'unexpected EOF' means truncation, 'type mismatch' means schema drift

Example fix

// before
cacheDir, _ := cache.DefaultDir()
_ = os.ReadFile(filepath.Join(cacheDir, key)) // decodes stale v1 gob data

// after
// include format version in cache key so old gob payloads are never decoded
db.Get(filepath.Join("v2", key), &out) // or purge: os.RemoveAll(cacheDir)
Defensive patterns

Strategy: fallback

Validate before calling

if len(b) == 0 {
    return nil, fmt.Errorf("cache entry empty, skipping decode")
}
// optionally verify a stored format version prefix before decoding
if !bytes.HasPrefix(b, []byte(cacheFormatV2)) {
    return nil, fmt.Errorf("cache format version mismatch")
}

Type guard

func isGobDecodeError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to gob decode")
}

Try / catch

if err := cache.Get(ctx, key, &out); err != nil {
    if isGobDecodeError(err) {
        log.Printf("corrupt/stale cache entry %q: %v — regenerating", key, err)
        _ = cache.Invalidate(ctx, key)
        out = recompute() // fall back to recomputation
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: Cache.Get hit an existing entry and decode() called gob.NewDecoder(bytes.NewReader(b)).Decode(data), but the bytes are not valid gob data or do not match the target type.

Common situations: Cache files written by a different version of the app (struct fields/types changed between releases, so gob type names no longer match); corrupted or truncated cache files (crash mid-write, disk issues); manually edited cache files; decoding into a different Go type than was encoded.

Related errors


AI-assisted analysis of golangci/golangci-lint@ed7a235d2d (2026-09-02). Data as JSON: /api/errors/862971f225b8da39. Report an issue: GitHub.