{"record":{"id":"d7d459d7c5d19dbd","repo":"abhigyanpatwari/GitNexus","slug":"unsupported-local-provider-provider-satisfies-n","errorCode":null,"errorMessage":"Unsupported local provider: ${provider satisfies never}","messagePattern":"Unsupported local provider: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/cli/wiki.ts","lineNumber":85,"sourceCode":"function isLocalProvider(\n  provider: LLMProvider | undefined,\n): provider is 'cursor' | 'claude' | 'codex' | 'opencode' | 'grok' {\n  return (\n    provider === 'cursor' ||\n    provider === 'claude' ||\n    provider === 'codex' ||\n    provider === 'opencode' ||\n    provider === 'grok'\n  );\n}\n\nfunction localModelConfigKey(provider: 'cursor' | 'claude' | 'codex' | 'opencode' | 'grok') {\n  if (provider === 'cursor') return 'cursorModel';\n  if (provider === 'claude') return 'claudeModel';\n  if (provider === 'codex') return 'codexModel';\n  if (provider === 'opencode') return 'opencodeModel';\n  if (provider === 'grok') return 'grokModel';\n  throw new Error(`Unsupported local provider: ${provider satisfies never}`);\n}\n\n/**\n * Prompt the user for input via stdin.\n */\nfunction prompt(question: string, hide = false): Promise<string> {\n  return new Promise((resolve) => {\n    const rl = readline.createInterface({\n      input: process.stdin,\n      output: process.stdout,\n    });\n\n    if (hide && process.stdin.isTTY) {\n      // Mask input for API keys\n      process.stdout.write(question);\n      let input = '';\n      process.stdin.setRawMode(true);\n      process.stdin.resume();","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/52924ef12c2290ceee4612526a828ec4cdf2047f/gitnexus/src/cli/wiki.ts#L67-L103","documentation":"Exhaustiveness guard at the end of localModelConfigKey in wiki.ts. The function maps each supported local provider ('cursor' | 'claude' | 'codex' | 'opencode' | 'grok') to its config key; the `provider satisfies never` throw is unreachable for well-typed callers and fires only when a provider string outside the union reaches it (e.g. via untyped JSON, an env var, or a newly added provider missing a mapping).","triggerScenarios":"Calling localModelConfigKey with a cast/loosely-typed value like `provider as any`, config/CLI input carrying a provider name not in the union (e.g. 'gemini-local'), or a new provider added to a wider type without extending this function's if-chain.","commonSituations":"Typo in a config file provider field ('claude ' with trailing space, wrong casing), third-party tooling injecting provider strings, or a contributor adding a provider to the wiki command without updating localModelConfigKey.","solutions":["Correct the provider value to one of: cursor, claude, codex, opencode, grok (exact, lowercase spelling).","If the value comes from config/JSON, validate it against the allowed provider union before calling.","If you added a new provider type, extend localModelConfigKey with a mapping branch for it before the throw.","Let TypeScript check exhaustiveness: keep the parameter typed as the literal union so `satisfies never` fails at compile time, not runtime."],"exampleFix":"// before\nfunction localModelConfigKey(provider: 'cursor' | 'claude' | 'codex' | 'opencode' | 'grok') {\n  if (provider === 'cursor') return 'cursorModel';\n  // ... grok mapped, new provider 'windsurf' missing\n\n// after\nfunction localModelConfigKey(provider: 'cursor' | 'claude' | 'codex' | 'opencode' | 'grok' | 'windsurf') {\n  if (provider === 'cursor') return 'cursorModel';\n  // ...\n  if (provider === 'windsurf') return 'windsurfModel';\n  throw new Error(`Unsupported local provider: ${provider satisfies never}`);","handlingStrategy":"type-guard","validationCode":"const LOCAL_PROVIDERS = ['cursor', 'claude', 'codex', 'opencode', 'grok'] as const;\ntype LocalProvider = typeof LOCAL_PROVIDERS[number];\nfunction parseProvider(v: string): LocalProvider {\n  if (!(LOCAL_PROVIDERS as readonly string[]).includes(v)) {\n    throw new Error(`Unsupported local provider: ${v}`);\n  }\n  return v as LocalProvider;\n}","typeGuard":"function isLocalProvider(v: unknown): v is 'cursor' | 'claude' | 'codex' | 'opencode' | 'grok' {\n  return v === 'cursor' || v === 'claude' || v === 'codex' || v === 'opencode' || v === 'grok';\n}","tryCatchPattern":"try {\n  const key = localModelConfigKey(provider);\n} catch (e) {\n  if (e.message.startsWith('Unsupported local provider:')) {\n    console.error(`${e.message} — expected one of cursor, claude, codex, opencode, grok`);\n  } else throw e;\n}","preventionTips":["Never cast provider strings with `as`; narrow them with a type guard parsed from config/CLI input.","When adding a provider to the union, extend localModelConfigKey in the same commit — `satisfies never` will flag it at compile time.","Normalize input (trim, lowercase) before matching provider names."],"tags":["typescript","exhaustiveness","provider-config","wiki"],"backgroundTag":"unsupported-provider","analyzedSha":"52924ef12c2290ceee4612526a828ec4cdf2047f","analyzedAt":"2026-09-01T13:15:02.810Z","contentChangedAt":"2026-09-01T13:15:02.810Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}