{"record":{"id":"26911992439cd87b","repo":"tailscale/tailscale","slug":"invalid-device-name-q","errorCode":null,"errorMessage":"invalid device name %q","messagePattern":"invalid device name %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tka/deeplink.go","lineNumber":57,"sourceCode":"type NewDeeplinkParams struct {\n\tNodeKey    string\n\tTLPub      string\n\tDeviceName string\n\tOSName     string\n\tLoginName  string\n}\n\n// NewDeeplink creates a signed deeplink using the authority's stateID as a\n// secret. This deeplink can then be validated by ValidateDeeplink.\nfunc (a *Authority) NewDeeplink(params NewDeeplinkParams) (string, error) {\n\tif params.NodeKey == \"\" || !strings.HasPrefix(params.NodeKey, \"nodekey:\") {\n\t\treturn \"\", fmt.Errorf(\"invalid node key %q\", params.NodeKey)\n\t}\n\tif params.TLPub == \"\" || !strings.HasPrefix(params.TLPub, \"tlpub:\") {\n\t\treturn \"\", fmt.Errorf(\"invalid tlpub %q\", params.TLPub)\n\t}\n\tif params.DeviceName == \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid device name %q\", params.DeviceName)\n\t}\n\tif params.OSName == \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid os name %q\", params.OSName)\n\t}\n\tif params.LoginName == \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid login name %q\", params.LoginName)\n\t}\n\n\tu := url.URL{\n\t\tScheme: DeeplinkTailscaleURLScheme,\n\t\tHost:   DeeplinkCommandSign,\n\t\tPath:   \"/v1/\",\n\t}\n\tv := url.Values{}\n\tv.Set(\"nk\", params.NodeKey)\n\tv.Set(\"tp\", params.TLPub)\n\tv.Set(\"dn\", params.DeviceName)\n\tv.Set(\"os\", params.OSName)","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/tka/deeplink.go#L39-L75","documentation":"NewDeeplink field check: DeviceName must be non-empty; it becomes the 'dn' query parameter of the sign deeplink so the approver can see which device they are signing. A zero-value struct field triggers this rejection before signing.","triggerScenarios":"NewDeeplinkParams built with DeviceName left \"\" - a forgotten profile field, an empty hostname, or a struct literal missing the field.","commonSituations":"Zero-value params structs; provisioning flows where the device hostname is not yet set when the deeplink is minted.","solutions":["Populate DeviceName from the node's hostname/profile data before calling","Default it to a generated name if the hostname is empty","Validate all five params in one helper before calling NewDeeplink"],"exampleFix":"// before\np := tka.NewDeeplinkParams{NodeKey: nk, TLPub: tp, OSName: runtime.GOOS}\n\n// after\np := tka.NewDeeplinkParams{NodeKey: nk, TLPub: tp, DeviceName: host.Name(), OSName: runtime.GOOS}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(p.DeviceName) == \"\" {\n    return errors.New(\"device name required before minting a sign deeplink\")\n}","typeGuard":"func nonEmpty(s string) bool { return strings.TrimSpace(s) != \"\" }","tryCatchPattern":null,"preventionTips":["Populate all five NewDeeplinkParams fields from one profile/hostname source","Run a single validateParams helper before calling NewDeeplink","Treat empty strings as bugs in the producer, not defaults"],"tags":["tka","deeplink","validation","required-field"],"backgroundTag":"missing-required-argument","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}