{"record":{"id":"def4b8dc623bd4c2","repo":"can1357/oh-my-pi","slug":"oauth-redirect-uri-must-not-include-surrounding-wh","errorCode":null,"errorMessage":"OAuth redirect URI must not include surrounding whitespace","messagePattern":"OAuth redirect URI must not include surrounding whitespace","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/mcp/oauth-flow.ts","lineNumber":138,"sourceCode":" */\nasync function readRegistrationFailureDetail(response: Response): Promise<string | undefined> {\n\ttry {\n\t\treturn truncateDetail(await response.text());\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n\treturn hostname === \"localhost\" || hostname === \"127.0.0.1\";\n}\n\nfunction resolveRedirectUri(redirectUri: string | undefined): string | undefined {\n\tconst configured = redirectUri;\n\tconst trimmed = configured?.trim();\n\tif (!trimmed) return undefined;\n\tif (trimmed !== configured) {\n\t\tthrow new Error(\"OAuth redirect URI must not include surrounding whitespace\");\n\t}\n\n\tconst parsed = new URL(configured);\n\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n\t\tthrow new Error(\"OAuth redirect URI must use http or https\");\n\t}\n\treturn configured;\n}\n\nfunction parseRedirectUri(redirectUri: string | undefined): URL | undefined {\n\treturn redirectUri ? new URL(redirectUri) : undefined;\n}\n\nfunction getUriPort(uri: URL): number {\n\tif (uri.port !== \"\") return Number(uri.port);\n\treturn uri.protocol === \"https:\" ? 443 : 80;\n}\n","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/oauth-flow.ts#L120-L156","documentation":"resolveRedirectUri validates the configured OAuth redirect URI before it is used in the authorization flow. It trims the value to decide whether it is present, and if the trimmed value differs from the configured one, the config contains leading/trailing whitespace — which would make the redirect URI mismatch the one registered at the authorization server. The library rejects it up front instead of failing later at the provider with redirect_uri mismatch.","triggerScenarios":"Setting oauth.redirectUri (or equivalent config) with leading or trailing spaces, e.g. copied from docs with a trailing space, newline from a heredoc/env var, or padding added by an editor — then starting the OAuth flow via resolveCallbackOptions/redirectUri.","commonSituations":"Copy-pasting a redirect URI from documentation or a browser address bar with a trailing space; environment variables that absorbed a newline; JSON/YAML configs with accidental whitespace around the string value.","solutions":["Remove the surrounding whitespace from the configured redirect URI","If the value comes from an env var, trim it at the source or fix how it is exported (e.g. quoted heredoc artifacts)","Validate config at load time: compare value to value.trim() and reject or trim before passing to the OAuth flow"],"exampleFix":"// before\n\"redirectUri\": \"  http://localhost:1455/auth/callback \"\n// after\n\"redirectUri\": \"http://localhost:1455/auth/callback\"","handlingStrategy":"validation","validationCode":"const uri = config.oauth?.redirectUri;\nif (uri != null && uri.trim() !== uri) {\n  throw new Error(`redirectUri has surrounding whitespace: ${JSON.stringify(uri)}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  startOAuthFlow(config);\n} catch (e) {\n  if (e.message.includes('must not include surrounding whitespace')) {\n    config.oauth.redirectUri = config.oauth.redirectUri.trim();\n    startOAuthFlow(config);\n  } else throw e;\n}","preventionTips":["Trim config values at load time before they reach OAuth validation","Avoid heredoc/env var artifacts by quoting values when exporting","Paste redirect URIs as plain text and inspect for trailing spaces","Add a config lint step that checks string fields for stray whitespace"],"tags":["oauth","configuration","validation","whitespace"],"backgroundTag":"invalid-redirect-uri","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}