temporalio/temporal · error

unreachable

Error message

unreachable

What it means

ParseDuration parses Nexus timeout header strings (e.g. "5m", "300ms") whose units are pre-validated by a regex. If input reaches the unit switch with a unit the switch does not handle, the function panics with "unreachable". The code assumes regex validation guarantees only ms/s/m units ever arrive, so hitting this panic means the validation regex and the switch have drifted apart.

Source

Thrown at common/nexus/nexusrpc/api.go:275

	m := durationRegexp.FindStringSubmatch(value)
	if len(m) == 0 {
		return 0, fmt.Errorf("invalid duration: %q", value)
	}
	v, err := strconv.ParseFloat(m[1], 64)
	if err != nil {
		return 0, err
	}

	switch m[2] {
	case "ms":
		return time.Millisecond * time.Duration(v), nil
	case "s":
		return time.Millisecond * time.Duration(v*1e3), nil
	case "m":
		return time.Millisecond * time.Duration(v*1e3*60), nil
	}
	// nolint:forbidigo // code is unreachable due to regex validation
	panic("unreachable")
}

// FormatDuration converts a duration into a string representation in millisecond resolution.
func FormatDuration(d time.Duration) string {
	return strconv.FormatInt(d.Milliseconds(), 10) + "ms"
}

const wrapperErrorMetadataKey = "unwrap-error"

// MarkAsWrapperError adds the wrapper-error metadata to the original failure of the given OperationError, which
// signals the Temporal codebase to unwrap the underlying failure as the failure cause.
// This is used as a shim for Temporal->Temporal calls. Temporal already has a Failure type that represents an
// OperationError and does not need to record this wrapper error.
func MarkAsWrapperError(failureConverter FailureConverter, opErr *nexus.OperationError) error {
	originalFailure, err := failureConverter.ErrorToFailure(opErr)
	if err != nil {
		return err
	}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Ensure every caller validates the input with the same regex used by parseRequestTimeoutHeader before calling ParseDuration
  2. If you need more units (h, d), add matching cases to the switch and update the regex together
  3. If the regex was changed, update ParseDuration's switch in common/nexus/nexusrpc/api.go to cover the new units

Example fix

// before
panic("unreachable")
// after
return 0, serviceerror.NewInvalidArgument(fmt.Sprintf("invalid duration unit: %q", unit))
Defensive patterns

Strategy: validation

Validate before calling

var durationRe = regexp.MustCompile(`^\d+(ms|s|m)$`)
func canParseDuration(s string) bool { return durationRe.MatchString(s) }
if !canParseDuration(input) {
	return serviceerror.NewInvalidArgument("invalid timeout duration")
}

Prevention

When it happens

Trigger: Calling ParseDuration directly (as tests do) with a duration string whose unit is not "ms", "s", or "m" (e.g. "5h", "5d"), bypassing the regex validation normally done by parseRequestTimeoutHeader.

Common situations: A new unit is added to the validation regex but not to the switch (or vice versa); direct unit tests feed unvalidated strings; another caller reuses ParseDuration without first running the header regex.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/917bf61cd35d9af7. Report an issue: GitHub.