juanfont/headscale · error
parsing duration: %w
Error message
parsing duration: %w
What it means
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.
Source
Thrown at cmd/headscale/cli/utils.go:334
out, err := formatOutput(result, override, format)
if err != nil {
return err
}
fmt.Println(out)
return nil
}
// expirationFromFlag parses the --expiration flag as a Prometheus-style
// duration (e.g. "90d", "1h") and returns an absolute time.
func expirationFromFlag(cmd *cobra.Command) (time.Time, error) {
durationStr, _ := cmd.Flags().GetString("expiration")
duration, err := model.ParseDuration(durationStr)
if err != nil {
return time.Time{}, fmt.Errorf("parsing duration: %w", err)
}
return time.Now().UTC().Add(time.Duration(duration)), nil
}
// confirmAction returns true when the user confirms a prompt, or when
// --force is set. Callers decide what to do when it returns false.
func confirmAction(cmd *cobra.Command, prompt string) bool {
force, _ := cmd.Flags().GetBool("force")
if force {
return true
}
return util.YesNo(prompt)
}
// renderTable prints a human-readable pterm table with the given header row
// and data rows, using the shared header styling.View on GitHub (pinned to 565fd254d0)
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
Example fix
# before headscale preauthkeys create --user myuser --expiration 90 # error: parsing duration: ... # after headscale preauthkeys create --user myuser --expiration 90d
Defensive patterns
Strategy: validation
Validate before calling
// Validate a Prometheus-style duration before passing it to --expiration.
func validDuration(s string) bool {
if s == "" {
return false
}
re := regexp.MustCompile(`^([0-9]+(?:\.[0-9]+)?)(ms|s|m|h|d|w|y)$`)
return re.MatchString(s)
}
// usage:
// if !validDuration(exp) { return fmt.Errorf("invalid --expiration %q: use e.g. 90d", exp) } Try / catch
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.
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- STUN address not set
- initial DERPMap is empty, Headscale requires at least one en
- failed to parse ApiKey
- failed to generate API key
- database type not supported
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/31bd7972816e5710.
Report an issue: GitHub.