{"record":{"id":"9ed3ba7bfaece8ff","repo":"tailscale/tailscale","slug":"received-empty-authkey-from-control-server","errorCode":null,"errorMessage":"received empty authkey from control server","messagePattern":"received empty authkey from control server","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"feature/identityfederation/identityfederation.go","lineNumber":89,"sourceCode":"\ttsClient := tailscale.NewClient(\"-\", tailscale.APIKey(accessToken))\n\ttsClient.UserAgent = \"tailscale-cli-identity-federation\"\n\ttsClient.BaseURL = args.BaseURL\n\n\tauthkey, _, err := tsClient.CreateKey(ctx, tailscale.KeyCapabilities{\n\t\tDevices: tailscale.KeyDeviceCapabilities{\n\t\t\tCreate: tailscale.KeyDeviceCreateCapabilities{\n\t\t\t\tReusable:      false,\n\t\t\t\tEphemeral:     ephemeral,\n\t\t\t\tPreauthorized: preauth,\n\t\t\t\tTags:          args.Tags,\n\t\t\t},\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unexpected error while creating authkey: %w\", err)\n\t}\n\tif authkey == \"\" {\n\t\treturn \"\", errors.New(\"received empty authkey from control server\")\n\t}\n\n\treturn authkey, nil\n}\n\nfunc parseOptionalAttributes(clientID string) (strippedID string, ephemeral bool, preauthorized bool, err error) {\n\tstrippedID, attrs, found := strings.Cut(clientID, \"?\")\n\tif !found {\n\t\treturn clientID, true, false, nil\n\t}\n\n\tparsed, err := url.ParseQuery(attrs)\n\tif err != nil {\n\t\treturn \"\", false, false, fmt.Errorf(\"failed to parse optional config attributes: %w\", err)\n\t}\n\n\tfor k := range parsed {\n\t\tswitch k {","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/feature/identityfederation/identityfederation.go#L71-L107","documentation":"The identity-federation authkey wrapper called the Tailscale control server's key-creation API and got a successful response, but the returned key string was empty. The code refuses to hand back an empty credential and fails with this error. An empty key with a 2xx response points at the control plane (or something between) rather than at the caller's inputs.","triggerScenarios":"Calling the wrapper around tailscale Keys().Create (Reusable=false, Ephemeral/Preauthorized parsed from the client-ID query attributes, Tags passed through) when the server responds 2xx with a missing/empty key field: a stub or headtest control server that omits the key, an API version that renamed the field, or a proxy that strips the response body.","commonSituations":"Running integration tests against a mock control server that returns {} or {\"usable\":true} with no key; API schema drift after a control-plane upgrade; a throttling layer returning 200 with an empty body.","solutions":["Inspect the raw key-creation HTTP response; the client expects a non-empty key string (e.g. tskey-auth-...) in the body","If you run the control server or a mock, make it return the key field in the create-key response","Verify the control server's API version matches what this client expects","Retry with backoff once or twice; a transient control-plane glitch can return 2xx with an empty body"],"exampleFix":"// before (mock control server)\nfunc handleCreateKey(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(200)\n\tio.WriteString(w, `{\"usable\":true}`)\n}\n// after\nfunc handleCreateKey(w http.ResponseWriter, r *http.Request) {\n\tw.WriteHeader(200)\n\tio.WriteString(w, `{\"key\":\"tskey-auth-k1234567890abcdef\",\"usable\":true}`)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"key, err := createAuthKey(ctx, args)\nif err != nil {\n\tif strings.Contains(err.Error(), \"empty authkey\") {\n\t\t// transient control-server behavior; retry with backoff, then fail loudly\n\t\tkey, err = retryWithBackoff(ctx, 3, func() (string, error) { return createAuthKey(ctx, args) })\n\t}\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"authkey creation failed: %w\", err)\n\t}\n}","preventionTips":["Run integration tests against a control server that implements key creation fully, returning a non-empty key","Log the raw response body (redacted) when the key comes back empty to catch schema drift early","Pin the control-server API version your client is built against"],"tags":["go","api","authkey","identity-federation","control-server"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}