vitessio/vitess · error

expected either pointer (for unary) or named interface (for

Error message

expected either pointer (for unary) or named interface (for streaming) rpc result type, got %T

What it means

The codegen walks each RPC method's result signature and only accepts a pointer type (unary reply, e.g. *vtctlservicepb.XxxResponse) or a named interface (streaming, e.g. VtctldServer_XxxStream). When the default case of the underlying-type switch hits an unexpected underlying shape, it panics with this message including %T of the offending type. It guards against generating invalid client stubs for a malformed service definition.

Source

Thrown at go/vt/vtctl/vtctldclient/codegen/main.go:168

				var argImports []typeArgImport
				localType, localImport, pkgPath, argImports, err = extractLocalNamedType(result)
				if err == nil {
					for _, imp := range argImports {
						importNames = addImport(imp.localImport, imp.pkgPath, importNames, imports)
					}
				}
				if err == nil && *local {
					// We need to get the pointer type returned by `stream.Recv()`
					// in the local case for the stream adapter.
					var recvType, recvImport, recvPkgPath string
					recvType, recvImport, recvPkgPath, err = extractRecvType(result)
					if err == nil {
						f.StreamMessage = buildParam("stream", recvImport, recvType, true)
						importNames = addImport(recvImport, recvPkgPath, importNames, imports)
					}
				}
			default:
				err = fmt.Errorf("expected either pointer (for unary) or named interface (for streaming) rpc result type, got %T", result.Type().Underlying())
			}
		default:
			err = fmt.Errorf("expected either pointer (for unary) or named interface (for streaming) rpc result type, got %T", result.Type())
		}

		if err != nil {
			panic(err)
		}

		f.Result = buildParam(result.Name(), localImport, localType, !f.IsStreaming)
		importNames = addImport(localImport, pkgPath, importNames, imports)
	}

	sort.Strings(importNames)
	sort.Strings(funcNames)

	def := &ClientInterfaceDef{
		PackageName: *pkgName,

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Fix the proto service method so it returns (*vtctlservicepb.XxxResponse, error) for unary or a named stream interface for server-streaming RPCs
  2. Regenerate the proto bindings (make proto / protoc) rather than hand-editing the generated Go interface
  3. If it's a new method pattern, update the codegen switch in main.go to support the new shape
  4. Confirm the proto file's rpc is properly declared streaming vs unary

Example fix

// before
GetKeyspace(ctx) (Keyspace, error)
// after
GetKeyspace(ctx) (*vtctlservicepb.GetKeyspaceResponse, error)
Defensive patterns

Strategy: validation

Validate before calling

// pre-check each method's result type shape before codegen
results := fn.Type().(type with 2 results)
if !isPointerOrNamedInterface(results[1].Type()) {
  return fmt.Errorf("bad rpc result type %s", results[1].Type())
}

Type guard

func validRPCResult(t types.Type) bool {
  switch u := t.Underlying().(type) {
  case *types.Pointer:
    return true
  case *types.Interface:
    named, ok := t.(*types.Named)
    return ok && u.NumMethods() > 0 && named != nil
  }
  return false
}

Try / catch

err := buildMethod(m)
if err != nil {
  panic(fmt.Errorf("method %s: %w", m.Name(), err)) // fail fast at codegen time
}

Prevention

When it happens

Trigger: Running codegen over a proto-generated service interface where a method's second return value is neither a pointer-to-struct nor a named interface — e.g. a plain value struct, a slice/map, or an unnamed interface type in the underlying switch's default branch.

Common situations: Hand-edited or regenerated proto service with a new method whose signature doesn't match the expected patterns; proto plugin version producing different return types; adding an RPC without the standard (resp *XxxResponse, err error) shape.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/b5ea03461cffa8d1. Report an issue: GitHub.