{"record":{"id":"31bd7972816e5710","repo":"juanfont/headscale","slug":"parsing-duration-w","errorCode":null,"errorMessage":"parsing duration: %w","messagePattern":"parsing duration: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/headscale/cli/utils.go","lineNumber":334,"sourceCode":"\n\tout, err := formatOutput(result, override, format)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfmt.Println(out)\n\n\treturn nil\n}\n\n// expirationFromFlag parses the --expiration flag as a Prometheus-style\n// duration (e.g. \"90d\", \"1h\") and returns an absolute time.\nfunc expirationFromFlag(cmd *cobra.Command) (time.Time, error) {\n\tdurationStr, _ := cmd.Flags().GetString(\"expiration\")\n\n\tduration, err := model.ParseDuration(durationStr)\n\tif err != nil {\n\t\treturn time.Time{}, fmt.Errorf(\"parsing duration: %w\", err)\n\t}\n\n\treturn time.Now().UTC().Add(time.Duration(duration)), nil\n}\n\n// confirmAction returns true when the user confirms a prompt, or when\n// --force is set.  Callers decide what to do when it returns false.\nfunc confirmAction(cmd *cobra.Command, prompt string) bool {\n\tforce, _ := cmd.Flags().GetBool(\"force\")\n\tif force {\n\t\treturn true\n\t}\n\n\treturn util.YesNo(prompt)\n}\n\n// renderTable prints a human-readable pterm table with the given header row\n// and data rows, using the shared header styling.","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/cmd/headscale/cli/utils.go#L316-L352","documentation":"Returned by expirationFromFlag when model.ParseDuration rejects the --expiration flag value. The parser expects a Prometheus-style duration: a number followed by a unit such as s, m, h, d, w, y (e.g. \"90d\", \"1h\"). An empty, unitless (\"90\"), misspelled, or negative value produces a parse error that is wrapped with this message.","triggerScenarios":"Running a CLI command that accepts --expiration (e.g. preauthkeys create) with a value like \"90\" (no unit), \"1day\" (invalid unit), \"-5h\" (negative), \"1.5 d\" (space), or an empty string; model.ParseDuration returns an error which is wrapped and returned to the command.","commonSituations":"Users coming from tools that accept plain day counts (expiration=90) instead of unit-suffixed durations; scripts migrated from older headscale versions or other CLIs with different duration syntax; typos in automation/CI flags.","solutions":["Suffix the value with a supported unit: --expiration 90d (also m, h, w, y are accepted by the Prometheus-style parser)","Check for stray characters: spaces, commas, or a missing/extra unit suffix","If the flag is optional in your workflow, verify you are not passing an empty string explicitly (--expiration \"\")","See the command's help output for the expected duration format"],"exampleFix":"# before\nheadscale preauthkeys create --user myuser --expiration 90\n# error: parsing duration: ...\n\n# after\nheadscale preauthkeys create --user myuser --expiration 90d","handlingStrategy":"validation","validationCode":"// Validate a Prometheus-style duration before passing it to --expiration.\nfunc validDuration(s string) bool {\n\tif s == \"\" {\n\t\treturn false\n\t}\n\tre := regexp.MustCompile(`^([0-9]+(?:\\.[0-9]+)?)(ms|s|m|h|d|w|y)$`)\n\treturn re.MatchString(s)\n}\n\n// usage:\n// if !validDuration(exp) { return fmt.Errorf(\"invalid --expiration %q: use e.g. 90d\", exp) }","typeGuard":null,"tryCatchPattern":"In Go: check the error returned by the CLI command; unwrap with errors.Unwrap to distinguish parse failures from other command errors and print the offending flag value.","preventionTips":["Always suffix durations with a unit (90d, 12h, 1w) in scripts","Encode expiration values in one shared variable/place rather than repeating literals","Add a shell-level assertion in CI: [[ \"$EXPIRATION\" =~ ^[0-9]+(ms|s|m|h|d|w|y)$ ]] before invoking the CLI"],"tags":["cli","flag-parsing","duration","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}