{"record":{"id":"cd74d778c6f2f635","repo":"router-for-me/CLIProxyAPI","slug":"plugin-sync-response-is-nil","errorCode":null,"errorMessage":"plugin sync response is nil","messagePattern":"plugin sync response is nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/home_sync.go","lineNumber":49,"sourceCode":"\nfunc (i *PluginSyncItem) Clear() {\n\tif i == nil {\n\t\treturn\n\t}\n\tClearResolvedAuthConfigs(i.Auth)\n\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)","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/home_sync.go#L31-L67","documentation":"PluginSyncResponse.Validate was invoked on a nil *PluginSyncResponse. Validate is a pointer method that dereferences fields, so it guards against nil receivers first. In practice this indicates a bug in the caller — the response object was never populated (e.g. a failed fetch returned nil and the error was ignored) before validation.","triggerScenarios":"Calling var r *PluginSyncResponse; r.Validate(now) or passing the result of a sync fetch whose error was ignored, leaving the pointer nil.","commonSituations":"Ignoring the error from the sync-fetch call and proceeding to Validate; early-return paths that skip assignment; refactor moving validation before population.","solutions":["Check the error returned by the sync-fetch call before calling Validate","Ensure the response variable is assigned from a successful fetch in every code path","Add a nil check on the response pointer at the call site for defensive clarity"],"exampleFix":"// before\nresp, _ := fetchSync(ctx)\nerr := resp.Validate(time.Now()) // panic risk / nil error\n\n// after\nresp, err := fetchSync(ctx)\nif err != nil {\n    return err\n}\nif err := resp.Validate(time.Now()); err != nil {\n    return err\n}","handlingStrategy":"validation","validationCode":"if resp == nil {\n    return errors.New(\"sync response is nil; fetch failed or was skipped\")\n}","typeGuard":"func hasSyncResponse(r *pluginstore.PluginSyncResponse) bool { return r != nil }","tryCatchPattern":"if err := resp.Validate(now); err != nil && strings.Contains(err.Error(), \"is nil\") {\n    // fix the fetch path that returned nil without propagating its error\n}","preventionTips":["Always handle the fetch error before using the response","Return early on fetch failures instead of falling through to validation","Lint for unchecked error returns on functions returning (ptr, error)"],"tags":["nil-check","api-misuse","validation","go"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}