temporalio/temporal · warning

invalid subsecond value

Error message

invalid subsecond value %v

What it means

After validating whole seconds, unmarshalTimestamp also bounds the subsecond component: it locates the '.' and the timezone marker and rejects timestamps whose fractional-second field is longer than 9 digits ('.999999999'). Longer or malformed fractional parts cannot be represented in nanosecond precision and are rejected with this error.

Solutions

  1. Truncate the producer's timestamp to nanosecond precision (9 digits) before sending.
  2. Use standard formatters (time.RFC3339Nano) which emit at most 9 fractional digits.
  3. Validate timestamps client-side before putting them in headers.
  4. If the value is not a real timestamp, fix the sender — this often indicates string concatenation bugs.

Example fix

// before
ts := fmt.Sprintf("%s.%sZ", datePart, nanoPart) // nanoPart has 12 digits
// after
ts := t.UTC().Format(time.RFC3339Nano) // at most 9 fractional digits
Defensive patterns

Strategy: validation

Validate before calling

if i := strings.IndexByte(s, '.'); i >= 0 {
    j := strings.IndexAny(s[i:], "Z-+")
    if j < 0 || (j-1) > 9 {
        return errors.New("subsecond precision exceeds 9 digits")
    }
}

Type guard

func isRFC3339Nano(s string) bool {
    _, err := time.Parse(time.RFC3339Nano, s)
    return err == nil && !strings.Contains(s, ".") || len(strings.SplitN(strings.Split(s, "Z")[0], ".", 2)) < 2 || len(strings.SplitN(strings.Split(s, "Z")[0], ".", 2)[1]) <= 9
}

Try / catch

t, err := time.Parse(time.RFC3339Nano, raw)
if err != nil {
    return fmt.Errorf("rejecting timestamp header %q: %w", raw, err)
}

Prevention

When it happens

Trigger: A timestamp header value like 2026-01-02T10:00:00.1234567890123Z (more than 9 fractional digits) or a malformed string where the timezone/index arithmetic finds an oversized subsecond span, from ServeHTTP.

Common situations: Non-Go clients emitting picosecond precision timestamps; hand-built headers in tests or scripts; custom formatting with more than nanosecond digits.

Related errors


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

Appendix: source

Thrown at common/nexus/nexusrpc/timestamp.go:42

// unmarshalTimestamp unmarshals a string into a Time instance. Uses RFC 3339, with some extra validation to ensure that
// seconds and subseconds are with an expected range.
// Copied from https://github.com/protocolbuffers/protobuf-go/blob/0b2c87d84c27802dae7248480444e22421ba577d/encoding/protojson/well_known_types.go#L749C1-L826C2
func unmarshalTimestamp(s string) (time.Time, error) {
	t, err := time.Parse(time.RFC3339Nano, s)
	if err != nil {
		return t, err
	}
	// Validate seconds.
	secs := t.Unix()
	if secs < minTimestampSeconds || secs > maxTimestampSeconds {
		return t, fmt.Errorf("second value out of range: %v", secs)
	}
	// Validate subseconds.
	i := strings.LastIndexByte(s, '.')  // start of subsecond field
	j := strings.LastIndexAny(s, "Z-+") // start of timezone field
	if i >= 0 && j >= i && j-i > len(".999999999") {
		return t, fmt.Errorf("invalid subsecond value %v", s)
	}
	return t, nil
}

View on GitHub (pinned to bde624efd1)