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
- Ensure every caller validates the input with the same regex used by parseRequestTimeoutHeader before calling ParseDuration
- If you need more units (h, d), add matching cases to the switch and update the regex together
- 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
- Always run the header regex validation before calling ParseDuration
- Never feed user-supplied strings to ParseDuration unvalidated
- Keep the validation regex and ParseDuration's unit switch in sync when adding units
- Add a table-driven unit test covering every regex-accepted unit
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
- invalid queue name, expected 4 fields
- cannot resolve Nexus endpoints partition owner: %w
- error loading nexus endpoints cache: %w
- error loading nexus endpoint cache: %w
- unable to list Nexus endpoints for namespace %s: %w
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/917bf61cd35d9af7.
Report an issue: GitHub.