{"record":{"id":"c0b700cdd187b3c7","repo":"router-for-me/CLIProxyAPI","slug":"credential-in-flight-staging-retention-must-be-pos","errorCode":null,"errorMessage":"credential-in-flight.staging-retention must be positive","messagePattern":"credential-in-flight\\.staging-retention must be positive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/credential_in_flight.go","lineNumber":57,"sourceCode":"\t\tMaxDetails:         DefaultInFlightMaxDetails,\n\t\tMaxStringBytes:     DefaultInFlightMaxStringBytes,\n\t\tStagingRetention:   \"1m\",\n\t}\n}\n\n// Durations parses and validates the in-flight observation durations.\nfunc (c CredentialInFlightConfig) Durations() (time.Duration, time.Duration, time.Duration, error) {\n\tsnapshotInterval, errSnapshot := time.ParseDuration(c.SnapshotInterval)\n\tif errSnapshot != nil || snapshotInterval <= 0 {\n\t\treturn 0, 0, 0, fmt.Errorf(\"credential-in-flight.snapshot-interval must be positive\")\n\t}\n\tstaleAfter, errStale := time.ParseDuration(c.StaleAfter)\n\tif errStale != nil || staleAfter <= 0 || snapshotInterval > staleAfter/3 {\n\t\treturn 0, 0, 0, fmt.Errorf(\"credential-in-flight.stale-after must be at least three snapshot intervals\")\n\t}\n\tstagingRetention, errRetention := time.ParseDuration(c.StagingRetention)\n\tif errRetention != nil || stagingRetention <= 0 {\n\t\treturn 0, 0, 0, fmt.Errorf(\"credential-in-flight.staging-retention must be positive\")\n\t}\n\treturn snapshotInterval, staleAfter, stagingRetention, nil\n}\n\n// Validate verifies the in-flight observation bounds.\nfunc (c CredentialInFlightConfig) Validate() error {\n\tif _, _, _, errDurations := c.Durations(); errDurations != nil {\n\t\treturn errDurations\n\t}\n\tif c.MaxPartBytes < 1024 || c.MaxPartCount <= 0 || c.MaxPartCount > DefaultInFlightMaxPartCount {\n\t\treturn fmt.Errorf(\"credential-in-flight part bounds are invalid\")\n\t}\n\tif c.MaxRevisionBytes < c.MaxPartBytes || c.MaxRevisionBytes > DefaultInFlightMaxRevisionBytes {\n\t\treturn fmt.Errorf(\"credential-in-flight.max-revision-bytes is outside hard bounds\")\n\t}\n\trequiredParts := (c.MaxRevisionBytes + c.MaxPartBytes - 1) / c.MaxPartBytes\n\tif requiredParts > c.MaxPartCount {\n\t\treturn fmt.Errorf(\"credential-in-flight.max-revision-bytes exceeds part capacity\")","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/config/credential_in_flight.go#L39-L75","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Set staging-retention to a positive duration, e.g. staging-retention: 1m (default).","Or remove the key so the built-in default (\"1m\") applies.","When constructing CredentialInFlightConfig in code, start from DefaultCredentialInFlightConfig() rather than the zero value."],"exampleFix":"# before (config.yaml)\ncredential-in-flight:\n  snapshot-interval: 2s\n  stale-after: 10s\n  staging-retention: 0s\n\n# after\ncredential-in-flight:\n  snapshot-interval: 2s\n  stale-after: 10s\n  staging-retention: 1m","handlingStrategy":"validation","validationCode":"// Go: validate retention string before use.\nret, err := time.ParseDuration(cfg.CredentialInFlight.StagingRetention)\nif err != nil || ret <= 0 {\n    return errors.New(\"staging-retention must be a positive Go duration (default 1m)\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Omit staging-retention to inherit the 1m default.","When building the config in code, start from DefaultCredentialInFlightConfig(), not the zero struct."],"tags":["config","validation","credential-in-flight","durations","retention"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}