router-for-me/CLIProxyAPI · error

credential-in-flight.staging-retention must be positive

Error message

credential-in-flight.staging-retention must be positive

What it means

CredentialInFlightConfig.Durations() parses 'credential-in-flight.staging-retention' and rejects it when parsing fails or the duration is <= 0. Staged in-flight revisions are retained for this window before expiry; a non-positive retention would drop them immediately and break staged accounting.

Source

Thrown at internal/config/credential_in_flight.go:57

		MaxDetails:         DefaultInFlightMaxDetails,
		MaxStringBytes:     DefaultInFlightMaxStringBytes,
		StagingRetention:   "1m",
	}
}

// Durations parses and validates the in-flight observation durations.
func (c CredentialInFlightConfig) Durations() (time.Duration, time.Duration, time.Duration, error) {
	snapshotInterval, errSnapshot := time.ParseDuration(c.SnapshotInterval)
	if errSnapshot != nil || snapshotInterval <= 0 {
		return 0, 0, 0, fmt.Errorf("credential-in-flight.snapshot-interval must be positive")
	}
	staleAfter, errStale := time.ParseDuration(c.StaleAfter)
	if errStale != nil || staleAfter <= 0 || snapshotInterval > staleAfter/3 {
		return 0, 0, 0, fmt.Errorf("credential-in-flight.stale-after must be at least three snapshot intervals")
	}
	stagingRetention, errRetention := time.ParseDuration(c.StagingRetention)
	if errRetention != nil || stagingRetention <= 0 {
		return 0, 0, 0, fmt.Errorf("credential-in-flight.staging-retention must be positive")
	}
	return snapshotInterval, staleAfter, stagingRetention, nil
}

// Validate verifies the in-flight observation bounds.
func (c CredentialInFlightConfig) Validate() error {
	if _, _, _, errDurations := c.Durations(); errDurations != nil {
		return errDurations
	}
	if c.MaxPartBytes < 1024 || c.MaxPartCount <= 0 || c.MaxPartCount > DefaultInFlightMaxPartCount {
		return fmt.Errorf("credential-in-flight part bounds are invalid")
	}
	if c.MaxRevisionBytes < c.MaxPartBytes || c.MaxRevisionBytes > DefaultInFlightMaxRevisionBytes {
		return fmt.Errorf("credential-in-flight.max-revision-bytes is outside hard bounds")
	}
	requiredParts := (c.MaxRevisionBytes + c.MaxPartBytes - 1) / c.MaxPartBytes
	if requiredParts > c.MaxPartCount {
		return fmt.Errorf("credential-in-flight.max-revision-bytes exceeds part capacity")

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Set staging-retention to a positive duration, e.g. staging-retention: 1m (default).
  2. Or remove the key so the built-in default ("1m") applies.
  3. When constructing CredentialInFlightConfig in code, start from DefaultCredentialInFlightConfig() rather than the zero value.

Example fix

# before (config.yaml)
credential-in-flight:
  snapshot-interval: 2s
  stale-after: 10s
  staging-retention: 0s

# after
credential-in-flight:
  snapshot-interval: 2s
  stale-after: 10s
  staging-retention: 1m
Defensive patterns

Strategy: validation

Validate before calling

// Go: validate retention string before use.
ret, err := time.ParseDuration(cfg.CredentialInFlight.StagingRetention)
if err != nil || ret <= 0 {
    return errors.New("staging-retention must be a positive Go duration (default 1m)")
}

Prevention

When it happens

Trigger: config.yaml with 'credential-in-flight:\n staging-retention: 0s', a negative value, or an invalid string such as '1 min'. The struct default is "1m", so the error only fires when the key is present with a bad value (or the config was built programmatically without normalization).

Common situations: Overriding retention to 0 to 'disable staging'; unit syntax errors (space between number and unit); programmatic Config construction in SDK embeds that leaves the field empty string.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/c0b700cdd187b3c7. Report an issue: GitHub.