{"record":{"id":"d064e91456a37545","repo":"decolua/9router","slug":"missing-kimchi-token","errorCode":null,"errorMessage":"\"Missing Kimchi token\"","messagePattern":"\"Missing Kimchi token\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/providers/kimchi.js","lineNumber":17,"sourceCode":"import { KIMCHI_CONFIG } from \"../constants/oauth.js\";\n\nconst kimchi = {\n  config: KIMCHI_CONFIG,\n  flowType: \"browser_token\",\n  buildAuthUrl: (config, redirectUri, state) => {\n    const baseUrl = (config.webAppUrl || \"https://app.kimchi.dev\").replace(/\\/+$/, \"\");\n    const params = new URLSearchParams({\n      callback: redirectUri,\n      state,\n    });\n    return `${baseUrl}/cli-auth?${params.toString()}`;\n  },\n  exchangeToken: async (config, token) => {\n    const accessToken = String(token || \"\").trim();\n    if (!accessToken) {\n      throw new Error(\"Missing Kimchi token\");\n    }\n\n    const validationUrl = config.validationUrl || \"https://api.cast.ai/v1/llm/openai/supported-providers\";\n    const validationRes = await fetch(validationUrl, {\n      method: \"GET\",\n      headers: {\n        Accept: \"application/json\",\n        Authorization: `Bearer ${accessToken}`,\n      },\n    });\n    if (!validationRes.ok) {\n      throw new Error(`Kimchi token validation failed: ${validationRes.status}`);\n    }\n\n    let userInfo = {};\n    if (config.userInfoUrl) {\n      try {\n        const userRes = await fetch(config.userInfoUrl, {","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/providers/kimchi.js#L1-L35","documentation":"Thrown by the Kimchi (CAST AI) OAuth provider's exchangeToken when the user-supplied token is empty or whitespace-only after trimming. The provider requires a manually pasted access token and refuses to send an empty Bearer credential upstream. It is a guard against pointless validation calls and misleading downstream 401s.","triggerScenarios":"Calling exchangeToken (the token-exchange step of Kimchi login) with token = null, undefined, \"\", or a string of only spaces — e.g. the UI field was left blank, clipboard paste failed, or the config value for the token was never set.","commonSituations":"User clicks 'connect' before pasting a token; an automation passes an unset env var; a stored credential was deleted and an empty string was persisted; trimming stripped a whitespace-only paste.","solutions":["Paste a valid CAST AI API token into the token field/env var before invoking exchangeToken.","Check upstream code for accidentally passing an empty string instead of the credential (e.g. process.env.KIMCHI_TOKEN unset).","Add a pre-call check: if (!token || !token.trim()) show a user-facing prompt instead of calling exchangeToken."],"exampleFix":"// before\nawait kimchiProvider.exchangeToken(config, userTokenInput);\n// after\nif (!userTokenInput || !userTokenInput.trim()) {\n  throw new Error('Please paste your CAST AI token first');\n}\nawait kimchiProvider.exchangeToken(config, userTokenInput.trim());","handlingStrategy":"validation","validationCode":"function hasKimchiToken(token) {\n  return typeof token === 'string' && token.trim().length > 0;\n}\nif (!hasKimchiToken(rawToken)) throw new Error('Provide a CAST AI token before connecting');","typeGuard":"const isNonEmptyString = (v) => typeof v === 'string' && v.trim().length > 0;","tryCatchPattern":"try {\n  await provider.exchangeToken(config, token);\n} catch (e) {\n  if (e.message === 'Missing Kimchi token') {\n    // prompt user to paste token\n  } else throw e;\n}","preventionTips":["Always trim token input and check non-empty before calling exchangeToken.","Disable the connect button in UI until the token field has content.","Validate env vars at startup so empty credentials fail fast."],"tags":["oauth","missing-credential","input-validation"],"backgroundTag":"missing-credential","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}