{"record":{"id":"a99a9f322b6dfd43","repo":"router-for-me/CLIProxyAPI","slug":"plugin-sync-response-missing-expires-at","errorCode":null,"errorMessage":"plugin sync response missing expires_at","messagePattern":"plugin sync response missing expires_at","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/home_sync.go","lineNumber":55,"sourceCode":"\ti.Auth = nil\n\ti.Manifest = Manifest{}\n}\n\ntype PluginSyncResponse struct {\n\tSchemaVersion int              `json:\"schema_version\"`\n\tExpiresAt     time.Time        `json:\"expires_at\"`\n\tItems         []PluginSyncItem `json:\"items\"`\n}\n\nfunc (r *PluginSyncResponse) Validate(now time.Time) error {\n\tif r == nil {\n\t\treturn fmt.Errorf(\"plugin sync response is nil\")\n\t}\n\tif r.SchemaVersion != PluginSyncSchemaVersion {\n\t\treturn fmt.Errorf(\"unsupported plugin sync schema_version %d\", r.SchemaVersion)\n\t}\n\tif r.ExpiresAt.IsZero() {\n\t\treturn fmt.Errorf(\"plugin sync response missing expires_at\")\n\t}\n\tif !now.Before(r.ExpiresAt) {\n\t\treturn fmt.Errorf(\"plugin sync response expired\")\n\t}\n\tseen := make(map[string]struct{}, len(r.Items))\n\tfor index := range r.Items {\n\t\titem := &r.Items[index]\n\t\tif errManifest := item.Manifest.Validate(); errManifest != nil {\n\t\t\treturn fmt.Errorf(\"plugin sync item %d: %w\", index, errManifest)\n\t\t}\n\t\tif errURLs := validatePluginSyncManifestURLs(item.Manifest); errURLs != nil {\n\t\t\treturn fmt.Errorf(\"plugin sync item %d: %w\", index, errURLs)\n\t\t}\n\t\tid := strings.TrimSpace(item.Manifest.ID)\n\t\tif _, exists := seen[id]; exists {\n\t\t\treturn fmt.Errorf(\"plugin sync response contains duplicate plugin %q\", id)\n\t\t}\n\t\tseen[id] = struct{}{}","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/home_sync.go#L37-L73","documentation":"The plugin sync response passed validation of schema_version but its expires_at time.Time is the zero value, meaning the field was absent or unparseable in the JSON. Every sync payload must carry a meaningful expiry so stale indexes are never used; a zero value is treated as malformed data.","triggerScenarios":"Validate on a sync response where the JSON lacked expires_at, it was null, or the timestamp format was not parseable by encoding/json (non-RFC3339).","commonSituations":"Hand-crafted or third-party sync indexes omitting expiry; a server bug dropping the field; a timestamp format change on the server.","solutions":["Inspect the raw sync JSON and confirm expires_at is present and RFC3339-formatted","Fix the sync server/index generator to always emit expires_at","Re-fetch the index from the official plugin store URL to get a well-formed payload"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if resp.ExpiresAt.IsZero() {\n    return errors.New(\"sync payload missing expires_at; treat as malformed\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"missing expires_at\") {\n    // discard payload, refetch; report to index operator if persistent\n}","preventionTips":["Index generators: always stamp expires_at (RFC3339)","Run the sync payload through Validate before caching it","Log raw payload shape when expiry validation fails to catch format drift"],"tags":["validation","sync","timestamp","json","go"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}