{"record":{"id":"c373e69528756cbb","repo":"tailscale/tailscale","slug":"tailscale-device-w","errorCode":null,"errorMessage":"tailscale.Device: %w","messagePattern":"tailscale\\.Device: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/tailscale/devices.go","lineNumber":176,"sourceCode":"\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, HandleErrorResponse(b, resp)\n\t}\n\n\tvar devices GetDevicesResponse\n\terr = json.Unmarshal(b, &devices)\n\treturn devices.Devices, err\n}\n\n// Device retrieved the details for a specific device.\n//\n// See the Device structure for the list of fields hidden for an external device.\n// The optional fields parameter specifies which fields of the devices to return; currently\n// only DeviceDefaultFields (equivalent to nil) and DeviceAllFields are supported.\n// Other values are currently undefined.\nfunc (c *Client) Device(ctx context.Context, deviceID string, fields *DeviceFieldsOpts) (device *Device, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"tailscale.Device: %w\", err)\n\t\t}\n\t}()\n\tpath := fmt.Sprintf(\"%s/api/v2/device/%s\", c.baseURL(), deviceID)\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", path, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Add fields.\n\tfieldStr := fields.addFieldsToQueryParameter()\n\tq := req.URL.Query()\n\tq.Add(\"fields\", fieldStr)\n\treq.URL.RawQuery = q.Encode()\n\n\tb, resp, err := c.sendRequest(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/client/tailscale/devices.go#L158-L194","documentation":"Wrapper error produced by a deferred func in Client.Device: any failure while fetching a single device's details (bad URL construction, transport error, non-200 response, or JSON decode of the Device payload) is wrapped as 'tailscale.Device: %w'. The underlying cause stays inspectable.","triggerScenarios":"Calling Client.Device(ctx, deviceID, fields) with a deviceID that does not exist (404 ErrResponse), a deviceID containing characters that break URL formatting (http.NewRequestWithContext parse error), an unauthorized token (401), an external device whose hidden fields produce unexpected JSON, or transport-level failures.","commonSituations":"Stale device ID cached after the device was removed from the tailnet, using a node ID from a different tailnet, expired API token, or passing an empty deviceID string so the URL path collapses.","solutions":["Confirm the deviceID currently exists by listing devices with Client.Devices and matching IDs.","errors.As(err, &tailscale.ErrResponse{}) and handle 404 (device gone), 401 (token), 400 (malformed ID).","Sanitize/URL-escape deviceID before calling if it may contain odd characters; valid IDs are numeric.","Check network reachability of the API endpoint if the error is a url.Error."],"exampleFix":"// before\n dev, err := c.Device(ctx, deviceID, tailscale.DeviceAllFields)\n if err != nil {\n     return err // opaque 'tailscale.Device: Status: 404, Message: ...'\n }\n\n// after\n dev, err := c.Device(ctx, deviceID, tailscale.DeviceAllFields)\n if err != nil {\n     var apiErr tailscale.ErrResponse\n     if errors.As(err, &apiErr) && apiErr.Status == http.StatusNotFound {\n         return fmt.Errorf(\"device %s no longer exists: %w\", deviceID, err)\n     }\n     return err\n }","handlingStrategy":"try-catch","validationCode":"// Validate before calling\nif expiry < 0 {\n    expiry = 0 // or return a config error\n}","typeGuard":"func validExpiry(d time.Duration) bool { return d >= 0 }","tryCatchPattern":"secret, meta, err := c.CreateKeyWithExpiry(ctx, caps, d)\nif err != nil {\n    if strings.Contains(err.Error(), \"expiry must be positive\") {\n        // recompute d from time.Until(target) and retry once\n    }\n    return \"\", nil, err\n}","preventionTips":["Never derive durations from past timestamps without clamping.","In tests, use time.Second not time.Millisecond for expiry.","Centralize key-creation in one helper that validates d >= 0 and (d == 0 || d >= time.Second)."],"tags":["go","tailscale","api-client","http","error-wrapping"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}