# 7 Common Pitfalls When Defining Protobuf Errors in Kratos

> Avoid common protobuf error definition pitfalls in Kratos. Learn to correctly annotate enums, use valid HTTP codes, and ensure the protoc-gen-go-errors plugin runs for seamless error handling.

- Repository: [Kratos/kratos](https://github.com/go-kratos/kratos)
- Tags: best-practices
- Published: 2026-03-02

---

**Developers using the Kratos framework often fail to annotate protobuf enums with the required `errors.code` or `errors.default_code` extensions, use invalid HTTP status codes outside the 1–600 range, or forget to invoke the `protoc-gen-go-errors` plugin, resulting in silent generation failures or runtime panics.**

The `go-kratos/kratos` microservices framework leverages protobuf definitions to generate strongly-typed error helpers that integrate with both HTTP and gRPC transports. However, the code generation process in `cmd/protoc-gen-go-errors` imposes strict validation rules that can cause silent failures when **protobuf errors in Kratos** are defined incorrectly. Understanding the generator's implementation ensures your error enums produce valid, usable Go code.

## Common Pitfalls in Error Definitions

### 1. Forgetting the `errors.code` or `errors.default_code` Extensions

Kratos generates Go error helpers from protobuf enums only when the enum values carry the `errors.code` option or the enum itself carries a `errors.default_code`. If these extensions are missing, the `protoc-gen-go-errors` plugin silently skips the file, leaving no generated error types.

According to the source code, the extensions are defined in [`errors/errors.proto`](https://github.com/go-kratos/kratos/blob/main/errors/errors.proto#L18-L25), while the generator reads them in [[`cmd/protoc-gen-go-errors/errors.go`](https://github.com/go-kratos/kratos/blob/main/cmd/protoc-gen-go-errors/errors.go)](https://github.com/go-kratos/kratos/blob/main/cmd/protoc-gen-go-errors/errors.go#L62-L78).

**Fix:**

```proto
syntax = "proto3";

import "errors/errors.proto";

enum UserError {
  // Apply a default HTTP code for the entire enum
  option (errors.default_code) = 400;

  // Individual error codes generate separate helper functions
  USER_NOT_FOUND = 0 [(errors.code) = 404];
  INVALID_INPUT = 1 [(errors.code) = 400];
}

```

### 2. Using Zero or Out-of-Range Status Codes

Kratos enforces that every error code be **greater than 0 and less than or equal to 600** (the valid HTTP range). A value of `0` or anything above `600` triggers a validation panic in the generator during the build phase.

The validation logic resides in [[`cmd/protoc-gen-go-errors/errors.go`](https://github.com/go-kratos/kratos/blob/main/cmd/protoc-gen-go-errors/errors.go)](https://github.com/go-kratos/kratos/blob/main/cmd/protoc-gen-go-errors/errors.go#L68-L82).

**Fix:**

```proto
// Correct: 400 is within the valid range
INVALID_INPUT = 0 [(errors.code) = 400];

// Incorrect: triggers a panic
INVALID_INPUT = 0 [(errors.code) = 0];     // Zero is invalid
INVALID_INPUT = 0 [(errors.code) = 700];    // Exceeds 600

```

### 3. Relying on Default Code Without Setting It

If an enum defines no per-value `errors.code` entries, the generator falls back to the enum-level `errors.default_code`. Omitting both makes the enum invisible to the generation loop, which skips enums when no valid codes are detected (specifically when the processed index remains at 0), as seen in lines 56–60 of [[`cmd/protoc-gen-go-errors/errors.go`](https://github.com/go-kratos/kratos/blob/main/cmd/protoc-gen-go-errors/errors.go)](https://github.com/go-kratos/kratos/blob/main/cmd/protoc-gen-go-errors/errors.go#L56-L60).

**Fix:**

```proto
enum AuthError {
  // Required when individual values lack errors.code
  option (errors.default_code) = 401;
  
  UNAUTHORIZED = 0;
  TOKEN_EXPIRED = 1;
}

```

### 4. Causing Naming Collisions Across Packages

Generated Go identifiers are formed from the **enum name** and **value name** (converted to CamelCase). Two different protobuf packages that define enums with identical names will produce conflicting Go symbols, causing compilation errors.

**Fix:**

- Use unique enum names per package (e.g., `UserError` vs. `OrderError`).
- Prefix value names with the domain (e.g., `USER_NOT_FOUND`, `ORDER_NOT_FOUND`).

### 5. Omitting the `protoc-gen-go-errors` Plugin

Even with correct protobuf definitions, error helpers will not appear unless the plugin is explicitly invoked during code generation. The repository's **Makefile** contains the build rules for compiling this plugin.

**Fix:**

Run the generator with the correct plugin flags:

```bash
protoc --go_out=. \
       --go-errors_out=. \
       --go-errors_opt=paths=source_relative \
       api/v1/user.proto

```

### 6. Ignoring Generated Metadata and Cause Chaining

Kratos errors support operational context via `WithMetadata` and `WithCause`. Forgetting these in service implementations strips away debugging information, such as field names or underlying repository errors.

Usage examples are verified in [[`errors/errors_test.go`](https://github.com/go-kratos/kratos/blob/main/errors/errors_test.go)](https://github.com/go-kratos/kratos/blob/main/errors/errors_test.go#L55-L59).

**Correct Usage:**

```go
return v1.UserError_INVALID_INPUT("INVALID_INPUT", "missing name").
    WithMetadata(map[string]string{"field": "name"}).
    WithCause(err)

```

### 7. Mismatching HTTP and gRPC Codes

When converting between protocols, Kratos maps specific gRPC codes to HTTP status codes. Using custom error codes outside standard mappings can result in unexpected HTTP responses. The mapping logic is defined in [[`transport/http/status/status.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/status/status.go)](https://github.com/go-kratos/kratos/blob/main/transport/http/status/status.go).

**Fix:**

- Restrict protobuf error codes to standard HTTP-compatible values (1–600).
- Use `codes.InvalidArgument` → `400` and similar standard mappings for cross-protocol consistency.

## Implementation Examples

### Protobuf Definition

```proto
syntax = "proto3";

package api.v1;

import "errors/errors.proto";

enum UserError {
  option (errors.default_code) = 400;

  USER_NOT_FOUND = 0 [(errors.code) = 404];
  INVALID_INPUT = 1 [(errors.code) = 400];
}

```

### Generated Go Helpers

The plugin generates functions like these in [`api/v1/user_errors.pb.go`](https://github.com/go-kratos/kratos/blob/main/api/v1/user_errors.pb.go):

```go
func UserError_USER_NOT_FOUND(reason, message string) *errors.Error {
    return errors.New(404, reason, message)
}

func UserError_INVALID_INPUT(reason, message string) *errors.Error {
    return errors.New(400, reason, message)
}

```

### Service Implementation

```go
import (
    "github.com/go-kratos/kratos/v2/errors"
    v1 "github.com/yourorg/yourrepo/api/v1"
)

func (s *UserService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserReply, error) {
    user, err := s.repo.Find(req.Id)
    if err != nil {
        return nil, v1.UserError_USER_NOT_FOUND("USER_NOT_FOUND", "user does not exist").
            WithCause(err).
            WithMetadata(map[string]string{"user_id": req.Id})
    }
    return &pb.GetUserReply{User: user}, nil
}

```

### Unit Testing

```go
func TestUserError(t *testing.T) {
    err := v1.UserError_INVALID_INPUT("INVALID_INPUT", "name is required").
        WithMetadata(map[string]string{"field": "name"})
    
    if !errors.IsBadRequest(err) {
        t.Fatalf("expected BadRequest, got %v", err)
    }
    
    if m := errors.Metadata(err); m["field"] != "name" {
        t.Fatalf("expected metadata field 'name'")
    }
}

```

## Summary

- **Always annotate** protobuf enums with `errors.code` or `errors.default_code` extensions defined in `errors/errors.proto`.
- **Validate status codes** remain between 1 and 600 to avoid generator panics in [`cmd/protoc-gen-go-errors/errors.go`](https://github.com/go-kratos/kratos/blob/main/cmd/protoc-gen-go-errors/errors.go).
- **Invoke the plugin** using `--go-errors_out` when running protoc commands.
- **Prevent naming collisions** by using domain-specific enum and value names.
- **Enrich errors** with `WithMetadata` and `WithCause` to preserve stack context and debugging data.
- **Honor HTTP mappings** in [`transport/http/status/status.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/status/status.go) when designing cross-protocol APIs.

## Frequently Asked Questions

### Why isn't my error enum generating Go helper functions?

The `protoc-gen-go-errors` plugin silently skips enums that lack the `(errors.code)` option on values or the `(errors.default_code)` option on the enum itself. Verify your imports include `errors/errors.proto` and that at least one of these extensions is present.

### What HTTP status codes are valid for Kratos protobuf errors?

Valid codes must be **greater than 0 and less than or equal to 600**, covering the standard HTTP status code range. The generator panics if it encounters code `0` or values exceeding `600` during validation in [`cmd/protoc-gen-go-errors/errors.go`](https://github.com/go-kratos/kratos/blob/main/cmd/protoc-gen-go-errors/errors.go).

### How do I add request context to a generated error?

Use the `WithMetadata` method to attach key-value pairs and `WithCause` to wrap underlying errors. These methods are available on `*errors.Error` return types from generated helpers and are demonstrated in [`errors/errors_test.go`](https://github.com/go-kratos/kratos/blob/main/errors/errors_test.go).

### Can I use the same enum name in different proto packages?

Avoid duplicate enum names across packages. The generator creates Go functions using the pattern `{EnumName}_{VALUE_NAME}`, which causes compilation conflicts if identical names exist in different packages within the same Go module.