{"record":{"id":"c443a38039b896b3","repo":"decolua/9router","slug":"missing-token","errorCode":null,"errorMessage":"Missing token","messagePattern":"Missing token","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/shared/components/OAuthModal.js","lineNumber":583,"sourceCode":"      // localStorage may be unavailable or data may be malformed - ignore silently\n    }\n\n    return () => {\n      window.removeEventListener(\"message\", handleMessage);\n      window.removeEventListener(\"storage\", handleStorage);\n      if (channel) channel.close();\n    };\n  }, [authData, exchangeTokens]);\n\n  // Handle manual URL input\n  const handleManualSubmit = async () => {\n    try {\n      setError(null);\n\n      // Paste-token mode (Trae/Windsurf): token goes straight to /exchange\n      if (authMode === \"paste-token\" && PASTE_TOKEN_PROVIDERS[provider]) {\n        const token = pasteToken.trim();\n        if (!token) throw new Error(\"Missing token\");\n        const res = await fetch(`/api/oauth/${provider}/exchange`, {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify({ code: token }),\n        });\n        const data = await res.json();\n        if (!res.ok) throw new Error(data.error);\n        setStep(\"success\");\n        onSuccess?.();\n        return;\n      }\n\n      const input = callbackUrl.trim();\n\n      // Trae/Windsurf proxy flow fallback (popup blocked): paste the full callback URL\n      if (PROXY_OAUTH_PROVIDERS.has(provider) && input) {\n        const res = await fetch(`/api/oauth/${provider}/exchange`, {\n          method: \"POST\",","sourceCodeStart":565,"sourceCodeEnd":601,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/shared/components/OAuthModal.js#L565-L601","documentation":"In handleManualSubmit's paste-token mode (Trae/Windsurf providers configured in PASTE_TOKEN_PROVIDERS), the modal throws when the pasted token is empty after trimming. No /exchange call is made; this is pure client-side input validation before sending the token to /api/oauth/{provider}/exchange.","triggerScenarios":"User selects paste-token auth mode and clicks submit with an empty or whitespace-only token field.","commonSituations":"User forgot to paste the token; pasted only whitespace; clipboard paste silently failed; field was cleared by a re-render before submit.","solutions":["Paste the API token into the input field and submit again","Check the clipboard actually contains the token (paste into a text editor first)","Trim stray whitespace/newlines from the pasted value"],"exampleFix":"// before\nconst token = pasteToken.trim();\nif (!token) throw new Error(\"Missing token\");\n// after\nconst token = pasteToken.trim();\nif (!token) throw new Error(\"Missing token — paste the token from your provider account page\");","handlingStrategy":"validation","validationCode":"const token = pasteToken.trim();\nif (!token) { setError(\"Paste a token first\"); return; } // disable submit until non-empty","typeGuard":"const hasToken = (s) => typeof s === 'string' && s.trim().length > 0;","tryCatchPattern":"null","preventionTips":["Disable the submit button while the token field is empty","Trim input before validation","Show inline hint about where to find the provider token"],"tags":["oauth","input-validation","paste-token"],"backgroundTag":"missing-required-input","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}