BigPizzaV3/CodexPlusPlus · error

请在设置中填写有效 API Key

Error message

请在设置中填写有效 API Key

What it means

For the external channel, apiConfig() requires a non-empty API key that contains no CR/LF characters. Header injection via newlines is blocked, and an empty key would produce an unusable Authorization header. This error means the key field was blank or contained line breaks.

Solutions

  1. Paste the provider API key into the API Key field in settings (keys are never persisted in tree checkpoints unless 'remember' is enabled)
  2. Strip whitespace/newlines from the pasted key before saving
  3. Re-enter the key if the app was restarted with remember=false, since it is intentionally not stored
  4. Get a new key from the provider if the old one is missing/revoked

Example fix

// before
const key = pastedKey; // 'sk-abc\n'
// after
const key = pastedKey.replace(/[\r\n]/g, '').trim();
Defensive patterns

Strategy: validation

Validate before calling

const key = String(input?.key || '').trim();
if (!key || /[\r\n]/.test(key)) throw new Error('请在设置中填写有效 API Key');

Type guard

function hasValidKey(c) { return typeof c?.key === 'string' && c.key.trim().length > 0 && !/[\r\n]/.test(c.key); }

Try / catch

let cfg;
try { cfg = apiConfig(formValues); } catch (e) { if (String(e).includes('API Key')) openSettingsAndFocusKeyField(); return; }

Prevention

When it happens

Trigger: apiConfig({channel:'external',...,key:''}) or key:'sk-abc\n malicious-header: x'; thrown through readApiForm/config when the settings form has no key or a multiline paste.

Common situations: User forgets to paste the API key when switching to external channel; key copied with a trailing newline from a terminal or PDF; key field contains multiple lines from a bad paste; remember=false and the stored config has no key after reload.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/68b3b9c8be3e8d3a. Report an issue: GitHub.

Appendix: source

Thrown at tools/conversation-canvas/external-api.mjs:16

// OpenAI-compatible Chat Completions. Never persist credentials in tree checkpoints.
export function apiEndpoint(raw){
  let url;try{url=new URL(String(raw).trim());}catch{throw Error('请输入完整的 HTTPS API 地址');}
  if(url.protocol!=='https:'||url.username||url.password||url.search||url.hash)throw Error('API 地址须使用 HTTPS,且不含账号、密码、查询参数或片段');
  const path=url.pathname.replace(/\/+$/,'');
  url.pathname=path.endsWith('/chat/completions')?path:(path||'/v1')+'/chat/completions';
  return url.href;
}

export function apiConfig(input){
  const channel=input?.channel==='external'?'external':'native';
  const value={channel,baseUrl:String(input?.baseUrl||'').trim(),model:String(input?.model||'').trim(),key:String(input?.key||'').trim(),remember:input?.remember===true,speed:input?.speed==='provider'?'provider':'fast',revision:input?.revision||crypto.randomUUID()};
  if(channel==='external'){
    value.endpoint=apiEndpoint(value.baseUrl);
    if(!value.model||value.model.length>200)throw Error('请填写 API 的模型名称');
    if(!value.key||/[\r\n]/.test(value.key))throw Error('请在设置中填写有效 API Key');
  }
  return value;
}

export function storedApiConfig(config){
  const {channel,baseUrl,model,remember,speed,revision}=config;
  return {channel,baseUrl,model,remember,speed,revision,...remember?{key:config.key}:{}};
}

export function apiError(error){
  if(error?.name==='AbortError')return error;
  if(error?.canvasApiLocal===true)return error;
  const code=Number(error?.status??error?.responseStatus);
  const hint={401:'密钥无效或已过期',403:'接口拒绝访问,请检查权限',404:'地址或模型不存在',408:'接口请求超时',413:'本批资料超过接口大小限制',429:'接口限流或额度不足'}[code];
  // Provider messages can echo request contents and Authorization; never display them.
  return Object.assign(Error(hint?`API ${code}:${hint}`:code>=400?`API 请求失败(HTTP ${code}),请检查服务状态`:'API 连接失败,请检查地址、网络及服务状态'),{retryable:!code||code===408||code===429||code>=500});
}

View on GitHub (pinned to b1ed92e5e4)