router-for-me/CLIProxyAPI · error
credential-in-flight.stale-after must be at least three snap
Error message
credential-in-flight.stale-after must be at least three snapshot intervals
What it means
CredentialInFlightConfig.Durations() requires stale-after to parse, be positive, and be at least three times snapshot-interval (snapshotInterval > staleAfter/3 fails). The 3x headroom guarantees a credential is only marked stale after several missed snapshots, tolerating transient hiccups without flapping in-flight accounting.
Source
Thrown at internal/config/credential_in_flight.go:53
MaxPartBytes: DefaultInFlightMaxPartBytes,
MaxPartCount: DefaultInFlightMaxPartCount,
MaxRevisionBytes: DefaultInFlightMaxRevisionBytes,
MaxAggregateGroups: DefaultInFlightMaxAggregateGroups,
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")View on GitHub (pinned to 78f0c4079e)
Solutions
- Set stale-after to at least 3x snapshot-interval, e.g. snapshot-interval: 2s with stale-after: 10s (documented default).
- If you shrink snapshot-interval, scale stale-after down proportionally but keep the ratio >= 3.
- Verify both strings parse as Go durations (no spaces, valid units).
Example fix
# before (config.yaml) credential-in-flight: snapshot-interval: 2s stale-after: 5s # after credential-in-flight: snapshot-interval: 2s stale-after: 10s
Defensive patterns
Strategy: validation
Validate before calling
// Go: enforce the 3x rule up front.
si, _ := time.ParseDuration(cfg.CredentialInFlight.SnapshotInterval)
sa, err := time.ParseDuration(cfg.CredentialInFlight.StaleAfter)
if err != nil || sa <= 0 || si > sa/3 {
return errors.New("stale-after must be >= 3 * snapshot-interval")
} Prevention
- Change snapshot-interval and stale-after together, preserving the >= 3x ratio.
- Default pair 2s/10s satisfies the rule; use it as the anchor.
When it happens
Trigger: config.yaml with 'credential-in-flight:\n snapshot-interval: 2s\n stale-after: 5s' (5s/3 = 1.67s < 2s, fails), or stale-after: 0s, negative, or unparseable. Needs snapshot-interval valid first, since it is checked before this.
Common situations: Tightening stale-after for faster reaping while forgetting the 3x rule; setting stale-after equal to snapshot-interval; copy-pasting a stale-after from an older config with a different snapshot cadence.
Related errors
- credential-in-flight.snapshot-interval must be positive
- credential-in-flight.staging-retention must be positive
- credential concurrency lifecycle durations must be positive
- credential concurrency limiter durations must be positive
- credential concurrency release max backoff must not be less
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/1c97de45cfe6435b.
Report an issue: GitHub.