k3s-io/k3s · error

token must not be empty

Error message

token must not be empty

What it means

clientaccess.parseToken (reached through ParseAndValidateToken / NewAccessInfo when joining a server or agent) rejects the empty string before doing any format work. It exists so callers fail fast with a clear message instead of proceeding with anonymous access.

Source

Thrown at pkg/clientaccess/token.go:216

// along with a bool indicating if the token was successfully parsed.
// Kubeadm-style tokens have ID/Secret not Username/Password and therefore will return false (invalid).
func ParseUsernamePassword(token string) (string, string, bool) {
	info, err := parseToken(token)
	if err != nil {
		return "", "", false
	}
	if info.BootstrapTokenString != nil {
		return "", "", false
	}
	return info.Username, info.Password, true
}

// parseToken parses a token into an Info struct
func parseToken(token string) (*Info, error) {
	var info Info

	if len(token) == 0 {
		return nil, errors.New("token must not be empty")
	}

	// Turn bare password or bootstrap token into full K10 token with empty CA hash,
	// for consistent parsing in the section below.
	if !strings.HasPrefix(token, tokenPrefix) {
		_, err := kubeadm.NewBootstrapTokenString(token)
		if err != nil {
			token = tokenPrefix + ":::" + token
		} else {
			token = tokenPrefix + "::" + token
		}
	}

	// Strip off the prefix.
	token = token[len(tokenPrefix):]

	// Split into CA hash and creds.
	parts := strings.SplitN(token, "::", 2)

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Provide a valid token: read it from the seed server's /var/lib/rancher/k3s/server/token or generate one with `k3s token create`
  2. Verify the env/secret actually carries a value: `[ -n "$K3S_TOKEN" ] && echo set`
  3. In Go callers, guard with a non-empty check before calling ParseAndValidateToken

Example fix

// before
info, err := clientaccess.ParseAndValidateToken(serverURL, os.Getenv("K3S_TOKEN"))

// after
token := strings.TrimSpace(os.Getenv("K3S_TOKEN"))
if token == "" {
    return errors.New("K3S_TOKEN must be set when joining")
}
info, err := clientaccess.ParseAndValidateToken(serverURL, token)
Defensive patterns

Strategy: validation

Validate before calling

token := strings.TrimSpace(cfg.Token)
if token == "" {
    return fmt.Errorf("token must not be empty when joining %s", cfg.JoinURL)
}
info, err := clientaccess.ParseAndValidateToken(cfg.JoinURL, token)

Type guard

func isNonEmptyToken(token string) bool { return strings.TrimSpace(token) != "" }

Try / catch

// parseToken errors are plain strings; match on content only after non-nil check
info, err := clientaccess.ParseAndValidateToken(server, token)
if err != nil {
    if strings.Contains(err.Error(), "token must not be empty") {
        return errors.New("join token missing: check K3S_TOKEN/secret injection")
    }
    return err
}

Prevention

When it happens

Trigger: Calling clientaccess.ParseAndValidateToken(serverURL, "") in Go, or starting k3s with `--server <url>` while K3S_TOKEN/--token is empty (or the config `token:` key is an empty string).

Common situations: K3S_TOKEN exported but set to empty (`K3S_TOKEN=` in a unit file); config.yaml containing `token: ""`; secrets mounted for the token that are empty on first rollout.

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/f3bd6ea40a63092e. Report an issue: GitHub.