{"record":{"id":"93870fad77e88c3b","repo":"router-for-me/CLIProxyAPI","slug":"auth-provider-poll-login-panic-v","errorCode":null,"errorMessage":"auth provider poll login panic: %v","messagePattern":"auth provider poll login panic: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/auth_provider.go","lineNumber":314,"sourceCode":"\t}\n\tvar pollMetadata map[string]any\n\tif len(metadata) > 0 {\n\t\tpollMetadata = metadata[0]\n\t}\n\treturn h.callPollLogin(ctx, *record, provider, state, pollMetadata)\n}\n\nfunc (h *Host) callPollLogin(ctx context.Context, record capabilityRecord, provider, state string, metadata map[string]any) (resp pluginapi.AuthLoginPollResponse, handled bool, err error) {\n\tauthProvider := record.plugin.Capabilities.AuthProvider\n\tif h == nil || authProvider == nil || h.isPluginFused(record.id) || !h.recordCurrent(record) {\n\t\treturn pluginapi.AuthLoginPollResponse{}, false, nil\n\t}\n\tdefer func() {\n\t\tif recovered := recover(); recovered != nil {\n\t\t\th.fusePlugin(record.id, \"AuthProvider.PollLogin\", recovered)\n\t\t\tresp = pluginapi.AuthLoginPollResponse{}\n\t\t\thandled = false\n\t\t\terr = fmt.Errorf(\"auth provider poll login panic: %v\", recovered)\n\t\t}\n\t}()\n\treq := pluginapi.AuthLoginPollRequest{\n\t\tProvider:   normalizeProviderID(provider),\n\t\tState:      strings.TrimSpace(state),\n\t\tHost:       h.hostConfigSummary(),\n\t\tHTTPClient: h.newHTTPClient(nil),\n\t\tMetadata:   cloneAnyMap(metadata),\n\t}\n\tresp, errPoll := authProvider.PollLogin(ctx, req)\n\tif errPoll != nil {\n\t\treturn pluginapi.AuthLoginPollResponse{}, true, errPoll\n\t}\n\treturn resp, true, nil\n}\n\nfunc (h *Host) RefreshAuth(ctx context.Context, auth *coreauth.Auth) (refreshed *coreauth.Auth, handled bool, err error) {\n\tif h == nil || auth == nil {","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/auth_provider.go#L296-L332","documentation":"This error is raised by the panic guard around a plugin's AuthProvider.PollLogin capability. If a plugin's poll-stage login handler panics while completing an OAuth-style flow, the host recovers it, fuses the plugin, and returns this error instead of crashing the process. The panic originated in plugin code, and the poll is treated as unhandled.","triggerScenarios":"Polling login completion for a plugin-backed provider (state + metadata passed from the start-login response) when the plugin's PollLogin panics: nil State string assumptions, malformed state parsing, type assertions on Metadata values, or nil HTTPClient usage.","commonSituations":"A plugin that expects the state token in a specific format but receives a trimmed/empty state (host applies strings.TrimSpace before passing); plugin written for a different poll response schema; partially completed login where metadata from StartLogin was lost.","solutions":["Inspect the fusePlugin log entry for 'AuthProvider.PollLogin' and the recovered value to pinpoint the panicking plugin.","Verify the state and metadata you pass to the poll endpoint are exactly what the plugin's StartLogin returned (do not re-serialize or trim them yourself).","Run the plugin's PollLogin standalone with the same State/Metadata to capture the full stack trace.","Fix or update the plugin so PollLogin validates inputs and returns errors; add nil checks for Metadata map access.","Restart the host to clear the fuse after fixing the plugin."],"exampleFix":"// plugin side, before\nfunc (p *Provider) PollLogin(ctx context.Context, req pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error) {\n\tparts := strings.Split(req.State, \":\")\n\treturn pluginapi.AuthLoginPollResponse{Token: parts[1]}, nil // panics: index out of range\n}\n\n// after\nfunc (p *Provider) PollLogin(ctx context.Context, req pluginapi.AuthLoginPollRequest) (pluginapi.AuthLoginPollResponse, error) {\n\tparts := strings.Split(req.State, \":\")\n\tif len(parts) != 2 {\n\t\treturn pluginapi.AuthLoginPollResponse{}, fmt.Errorf(\"poll login: malformed state\")\n\t}\n\treturn pluginapi.AuthLoginPollResponse{Token: parts[1]}, nil\n}","handlingStrategy":"try-catch","validationCode":"if strings.TrimSpace(state) == \"\" {\n    return fmt.Errorf(\"poll login requires the state returned by start login\")\n}","typeGuard":"func validPollInput(state string, metadata map[string]any) bool {\n    return strings.TrimSpace(state) != \"\" && metadata != nil\n}","tryCatchPattern":"resp, handled, err := host.CallPollLogin(ctx, provider, state, metadata)\nif err != nil {\n    if strings.Contains(err.Error(), \"poll login panic\") {\n        log.WithField(\"provider\", provider).WithError(err).Error(\"plugin crashed during poll; user must restart login\")\n    }\n    return err\n}\nif !handled {\n    // plugin gone (fused/removed): restart the login flow from start\n}","preventionTips":["Pass the start-login response's state/metadata through verbatim; do not trim or re-encode them yourself.","Persist the start-login state so a poll retry after plugin failure can restart cleanly.","Test the full start+poll cycle after every plugin upgrade."],"tags":["plugin","panic","auth","oauth","pluginhost"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}